diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..302ce48 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,6 @@ +[target.'cfg(unix)'] +runner = "sudo -E" + +[alias] # command aliases +rr = "run --release" +bb = "build --release" diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e855853 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +# Xcode +Apple/ + +# Rust +target/ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 20c920e..9a9b5b2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,4 @@ -* @conradev +* @conradev @malted @JettChenT @jdogcoder +burrow/ @conradev @malted @JettChenT @jdogcoder @Muirrum +tun/ @conradev @malted @JettChenT @jdogcoder @Muirrum +burrow-gtk/ @conradev @malted @JettChenT @jdogcoder @davnotdev diff --git a/.github/actions/archive/action.yml b/.github/actions/archive/action.yml index c34bd3c..e49eb0d 100644 --- a/.github/actions/archive/action.yml +++ b/.github/actions/archive/action.yml @@ -26,18 +26,18 @@ runs: run: | echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8 - xcodebuild archive \ + xcodebuild clean archive \ -allowProvisioningUpdates \ -allowProvisioningDeviceRegistration \ + -skipPackagePluginValidation \ + -skipMacroValidation \ + -onlyUsePackageVersionsFromResolvedFile \ -authenticationKeyID ${{ inputs.app-store-key-id }} \ -authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \ -authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \ - -onlyUsePackageVersionsFromResolvedFile \ -scheme '${{ inputs.scheme }}' \ -destination '${{ inputs.destination }}' \ -archivePath '${{ inputs.archive-path }}' \ -resultBundlePath BuildResults.xcresult - ./Tools/xcresulttool-github BuildResults.xcresult - rm -rf AuthKey_${{ inputs.app-store-key-id }}.p8 diff --git a/.github/actions/build-for-testing/action.yml b/.github/actions/build-for-testing/action.yml index 9691122..185c4ab 100644 --- a/.github/actions/build-for-testing/action.yml +++ b/.github/actions/build-for-testing/action.yml @@ -18,18 +18,36 @@ inputs: runs: using: composite steps: - - shell: bash + - name: Xcode Cache + uses: actions/cache@v3 + with: + path: | + Apple/PackageCache + Apple/SourcePackages + Apple/DerivedData + key: ${{ runner.os }}-${{ inputs.scheme }}-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-${{ inputs.scheme }}-${{ hashFiles('**/Package.resolved') }} + ${{ runner.os }}-${{ inputs.scheme }}- + ${{ runner.os }}- + - name: Build + shell: bash working-directory: Apple run: | echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8 - xcodebuild clean build-for-testing \ + xcodebuild build-for-testing \ -allowProvisioningUpdates \ -allowProvisioningDeviceRegistration \ + -skipPackagePluginValidation \ + -skipMacroValidation \ + -onlyUsePackageVersionsFromResolvedFile \ -authenticationKeyID ${{ inputs.app-store-key-id }} \ -authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \ -authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \ - -onlyUsePackageVersionsFromResolvedFile \ + -clonedSourcePackagesDirPath SourcePackages \ + -packageCachePath $PWD/PackageCache \ + -derivedDataPath $PWD/DerivedData \ -scheme '${{ inputs.scheme }}' \ -destination '${{ inputs.destination }}' \ -resultBundlePath BuildResults.xcresult diff --git a/.github/actions/download-profiles/action.yml b/.github/actions/download-profiles/action.yml new file mode 100644 index 0000000..32b615c --- /dev/null +++ b/.github/actions/download-profiles/action.yml @@ -0,0 +1,30 @@ +name: Download Provisioning Profiles +inputs: + app-store-key: + description: App Store key in PEM PKCS#8 format + required: true + app-store-key-id: + description: App Store key ID + required: true + app-store-key-issuer-id: + description: App Store key issuer ID + required: true +runs: + using: composite + steps: + - shell: bash + env: + FASTLANE_OPT_OUT_USAGE: 'YES' + run: | + APP_STORE_KEY=$(echo "${{ inputs.app-store-key }}" | jq -sR .) + cat << EOF > api-key.json + { + "key_id": "${{ inputs.app-store-key-id }}", + "issuer_id": "${{ inputs.app-store-key-issuer-id }}", + "key": $APP_STORE_KEY + } + EOF + + fastlane sigh download_all --api_key_path api-key.json + + rm -rf api-key.json diff --git a/.github/actions/export/action.yml b/.github/actions/export/action.yml index bf007a7..75b748f 100644 --- a/.github/actions/export/action.yml +++ b/.github/actions/export/action.yml @@ -1,4 +1,4 @@ -name: Notarize +name: Export inputs: app-store-key: description: App Store key in PEM PKCS#8 format @@ -12,11 +12,8 @@ inputs: archive-path: description: Xcode archive path required: true - destination: - description: The Xcode export destination. This can either be "export" or "upload" - required: true - method: - description: The Xcode export method. This can be one of app-store, validation, ad-hoc, package, enterprise, development, developer-id, or mac-application. + export-options: + description: The export options in JSON format required: true export-path: description: The path to export the archive to @@ -24,19 +21,20 @@ inputs: runs: using: composite steps: - - id: notarize - shell: bash + - shell: bash working-directory: Apple run: | echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8 - echo '{"destination":"${{ inputs.destination }}","method":"${{ inputs.method }}"}' \ - | plutil -convert xml1 -o ExportOptions.plist - + echo '${{ inputs.export-options }}' | plutil -convert xml1 -o ExportOptions.plist - xcodebuild \ -exportArchive \ -allowProvisioningUpdates \ -allowProvisioningDeviceRegistration \ + -skipPackagePluginValidation \ + -skipMacroValidation \ + -onlyUsePackageVersionsFromResolvedFile \ -authenticationKeyID ${{ inputs.app-store-key-id }} \ -authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \ -authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \ diff --git a/.github/actions/notarize/action.yml b/.github/actions/notarize/action.yml new file mode 100644 index 0000000..efd2159 --- /dev/null +++ b/.github/actions/notarize/action.yml @@ -0,0 +1,25 @@ +name: Notarize +inputs: + app-store-key: + description: App Store key in PEM PKCS#8 format + required: true + app-store-key-id: + description: App Store key ID + required: true + app-store-key-issuer-id: + description: App Store key issuer ID + required: true +runs: + using: composite + steps: + - id: notarize + shell: bash + working-directory: Apple + run: | + echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8 + + ditto -c -k --keepParent Release/Burrow.app Upload.zip + xcrun notarytool submit --wait --issuer ${{ inputs.app-store-key-issuer-id }} --key-id ${{ inputs.app-store-key-id }} --key "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" Upload.zip + xcrun stapler staple Release/Burrow.app + + rm -rf AuthKey_${{ inputs.app-store-key-id }}.p8 Release diff --git a/.github/actions/test-without-building/action.yml b/.github/actions/test-without-building/action.yml index 5903d07..a097d4a 100644 --- a/.github/actions/test-without-building/action.yml +++ b/.github/actions/test-without-building/action.yml @@ -18,9 +18,6 @@ inputs: runs: using: composite steps: - - shell: bash - id: vars - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - shell: bash working-directory: Apple run: | @@ -28,10 +25,10 @@ runs: -scheme '${{ inputs.scheme }}' \ -destination '${{ inputs.destination }}' \ ${{ inputs.test-plan && '-testPlan ' }}${{ inputs.test-plan }} \ - -resultBundlePath "${{ inputs.artifact-prefix }}-${{ steps.vars.outputs.sha_short }}.xcresult" + -resultBundlePath "${{ inputs.artifact-prefix }}.xcresult" - uses: kishikawakatsumi/xcresulttool@v1 if: always() with: - path: Apple/${{ inputs.artifact-prefix }}-${{ steps.vars.outputs.sha_short }}.xcresult + path: Apple/${{ inputs.artifact-prefix }}.xcresult title: ${{ inputs.check-name }} show-passed-tests: false diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml new file mode 100644 index 0000000..bd29b07 --- /dev/null +++ b/.github/workflows/build-appimage.yml @@ -0,0 +1,29 @@ +name: Build AppImage +on: + push: + branches: + - main + pull_request: + branches: + - "*" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + appimage: + name: Build AppImage + runs-on: ubuntu-latest + container: docker + steps: + - uses: actions/checkout@v4 + - name: Build AppImage + run: | + docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile + docker create --name temp appimage-builder + docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage . + docker rm temp + - uses: actions/upload-artifact@v4 + name: Upload to GitHub + with: + name: AppImage + path: Burrow-x86_64.AppImage diff --git a/.github/workflows/build-apple.yml b/.github/workflows/build-apple.yml index d1dba49..7ae8c4c 100644 --- a/.github/workflows/build-apple.yml +++ b/.github/workflows/build-apple.yml @@ -1,33 +1,45 @@ -name: Apple Build +name: Build Apple Apps on: push: - branches: + branches: - main pull_request: branches: - "*" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: build: name: Build App (${{ matrix.platform }}) - runs-on: macos-12 + runs-on: macos-14 strategy: fail-fast: false matrix: include: - - scheme: Burrow + - scheme: App destination: generic/platform=iOS platform: iOS sdk-name: iphoneos - - scheme: Burrow - destination: platform=iOS Simulator,OS=16.2,name=iPhone 14 Pro + rust-targets: + - aarch64-apple-ios + - scheme: App + destination: platform=iOS Simulator,OS=18.0,name=iPhone 15 Pro platform: iOS Simulator sdk-name: iphonesimulator - - scheme: Burrow + rust-targets: + - aarch64-apple-ios-sim + - x86_64-apple-ios + - scheme: App destination: platform=macOS platform: macOS sdk-name: macos + rust-targets: + - x86_64-apple-darwin + - aarch64-apple-darwin env: - DEVELOPER_DIR: /Applications/Xcode_14.2.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer + PROTOC_PATH: /opt/homebrew/bin/protoc steps: - name: Checkout uses: actions/checkout@v3 @@ -39,6 +51,13 @@ jobs: with: certificate: ${{ secrets.DEVELOPER_CERT }} password: ${{ secrets.DEVELOPER_CERT_PASSWORD }} + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ join(matrix.rust-targets, ', ') }} + - name: Install Protobuf + shell: bash + run: brew install protobuf - name: Build id: build uses: ./.github/actions/build-for-testing @@ -48,7 +67,7 @@ jobs: app-store-key: ${{ secrets.APPSTORE_KEY }} app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} - - name: Xcode Unit Test + - name: Run Unit Tests if: ${{ matrix.xcode-unit-test != '' }} continue-on-error: true uses: ./.github/actions/test-without-building @@ -58,7 +77,7 @@ jobs: test-plan: ${{ matrix.xcode-unit-test }} artifact-prefix: unit-tests-${{ matrix.sdk-name }} check-name: Xcode Unit Tests (${{ matrix.platform }}) - - name: Xcode UI Test + - name: Run UI Tests if: ${{ matrix.xcode-ui-test != '' }} continue-on-error: true uses: ./.github/actions/test-without-building @@ -67,4 +86,4 @@ jobs: destination: ${{ matrix.destination }} test-plan: ${{ matrix.xcode-ui-test }} artifact-prefix: ui-tests-${{ matrix.sdk-name }} - check-name: Xcode UI Tests (${{ matrix.platform }}) + check-name: Xcode UI Tests (${{ matrix.platform }}) \ No newline at end of file diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml new file mode 100644 index 0000000..6a3dae1 --- /dev/null +++ b/.github/workflows/build-docker.yml @@ -0,0 +1,49 @@ +name: Build Docker +on: + push: + branches: + - main + pull_request: + branches: + - "*" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + build: + name: Build Docker Image + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Setup QEMU + uses: docker/setup-qemu-action@v2 + with: + platforms: arm64 + - name: Setup BuildKit + uses: docker/setup-buildx-action@v2 + - name: Authenticate + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=sha + type=match,pattern=builds/(.*),group=1 + type=raw,value=latest,enable={{is_default_branch}} + - name: Build and Push + uses: docker/build-push-action@v4 + with: + platforms: ${{ github.event_name != 'pull_request' && 'linux/amd64,linux/arm64' || 'linux/amd64' }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/build-flatpak.yml b/.github/workflows/build-flatpak.yml new file mode 100644 index 0000000..d74eec3 --- /dev/null +++ b/.github/workflows/build-flatpak.yml @@ -0,0 +1,16 @@ +on: workflow_dispatch +name: Build Flatpak +jobs: + flatpak: + name: Build Flatpak + runs-on: ubuntu-latest + container: + image: bilelmoussaoui/flatpak-github-actions:gnome-45 + options: --privileged + steps: + - uses: actions/checkout@v4 + - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: Burrow.flatpak + manifest-path: burrow-gtk/build-aux/com.hackclub.burrow.devel.json + cache-key: flatpak-builder-${{ github.sha }} diff --git a/.github/workflows/build-rpm.yml b/.github/workflows/build-rpm.yml new file mode 100644 index 0000000..029bf16 --- /dev/null +++ b/.github/workflows/build-rpm.yml @@ -0,0 +1,17 @@ +on: workflow_dispatch +name: Build RPM +jobs: + build: + name: Build RPM + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + - name: Install RPM + run: cargo install cargo-generate-rpm + - name: Build RPM + run: | + cargo build --release + strip -s target/release/burrow + - name: Build RPM + run: cargo generate-rpm -p burrow diff --git a/.github/workflows/build-rust.yml b/.github/workflows/build-rust.yml index 8048a3a..95fc628 100644 --- a/.github/workflows/build-rust.yml +++ b/.github/workflows/build-rust.yml @@ -1,7 +1,7 @@ -name: Rust Build +name: Build Rust Crate on: push: - branches: + branches: - main pull_request: branches: @@ -17,28 +17,38 @@ jobs: platform: Linux packages: - gcc-aarch64-linux-gnu - targets: + test-targets: - x86_64-unknown-linux-gnu - - aarch64-unknown-linux-gnu - - os: macos-12 - platform: macOS targets: + - aarch64-unknown-linux-gnu + - os: macos-13 + platform: macOS (Intel) + xcode: /Applications/Xcode_15.2.app + test-targets: - x86_64-apple-darwin + targets: + - x86_64-apple-ios + - os: macos-14 + platform: macOS + xcode: /Applications/Xcode_16.0.app + test-targets: - aarch64-apple-darwin + targets: - aarch64-apple-ios - aarch64-apple-ios-sim - - x86_64-apple-ios - os: windows-2022 platform: Windows - targets: + test-targets: - x86_64-pc-windows-msvc + targets: - aarch64-pc-windows-msvc runs-on: ${{ matrix.os }} env: - DEVELOPER_DIR: /Applications/Xcode_14.2.app/Contents/Developer + DEVELOPER_DIR: ${{ matrix.xcode }}/Contents/Developer CARGO_INCREMENTAL: 0 CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc RUST_BACKTRACE: short + PROTOC_VERSION: 3.25.1 steps: - name: Checkout uses: actions/checkout@v3 @@ -48,13 +58,28 @@ jobs: - name: Install Packages if: matrix.os == 'ubuntu-latest' shell: bash - run: sudo apt-get install -y ${{ join(matrix.packages, ' ') }} + run: | + sudo apt-get update + sudo apt-get install -y ${{ join(matrix.packages, ' ') }} + - name: Configure LLVM + if: matrix.os == 'windows-2022' + shell: bash + run: echo "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm\x64\bin" >> $GITHUB_PATH + - name: Install protoc + uses: taiki-e/install-action@v2 + with: + tool: protoc@${{ env.PROTOC_VERSION }} - name: Install Rust - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: rustfmt targets: ${{ join(matrix.targets, ', ') }} + - name: Setup Rust Cache + uses: Swatinem/rust-cache@v2 - name: Build shell: bash - run: cargo build --verbose --workspace --all-features --target ${{ join(matrix.targets, ' --target ') }} + run: cargo build --verbose --workspace --all-features --target ${{ join(matrix.targets, ' --target ') }} --target ${{ join(matrix.test-targets, ' --target ') }} + - name: Test + shell: bash + run: cargo test --verbose --workspace --all-features --target ${{ join(matrix.test-targets, ' --target ') }} \ No newline at end of file diff --git a/.github/workflows/lint-git.yml b/.github/workflows/lint-git.yml index aefe199..2f7c72e 100644 --- a/.github/workflows/lint-git.yml +++ b/.github/workflows/lint-git.yml @@ -8,13 +8,14 @@ jobs: name: Git Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - name: Install Gitlint - shell: bash - run: python -m pip install gitlint - - name: Run Gitlint - shell: bash - run: gitlint --commits "${{ github.event.pull_request.base.sha }}..HEAD" + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: Install + shell: bash + run: python -m pip install gitlint + - name: Lint + shell: bash + run: gitlint --commits "${{ github.event.pull_request.base.sha }}..HEAD" diff --git a/.github/workflows/lint-swift.yml b/.github/workflows/lint-swift.yml index 7e62afd..857f575 100644 --- a/.github/workflows/lint-swift.yml +++ b/.github/workflows/lint-swift.yml @@ -1,8 +1,5 @@ name: Swift Lint on: - push: - branches: - - main pull_request: branches: - "*" @@ -14,8 +11,6 @@ jobs: image: ghcr.io/realm/swiftlint:latest steps: - name: Checkout - uses: actions/checkout@v3 - with: - ssh-key: ${{ secrets.DEPLOY_KEY }} + uses: actions/checkout@v4 - name: Lint - run: swiftlint lint --reporter github-actions-logging + run: swiftlint lint --strict --reporter github-actions-logging diff --git a/.github/workflows/release-apple.yml b/.github/workflows/release-apple.yml index 8b8a76c..c869d6a 100644 --- a/.github/workflows/release-apple.yml +++ b/.github/workflows/release-apple.yml @@ -1,65 +1,119 @@ -name: Build Apple Release +name: Release (Apple) on: release: types: - created jobs: build: - name: Build ${{ matrix.configuration['platform'] }} Release - runs-on: macos-12 + name: Build ${{ matrix.platform }} Release + runs-on: macos-14 + permissions: + contents: write strategy: fail-fast: false matrix: - configuration: - - scheme: App (iOS) - destination: generic/platform=iOS - platform: iOS - method: ad-hoc - artifact-file: Apple/Release/Burrow.ipa - - scheme: App (macOS) - destination: generic/platform=macOS - platform: macOS - method: mac-application - artifact-file: Burrow.app.txz + include: + - platform: iOS + rust-targets: + - aarch64-apple-ios + - platform: macOS + rust-targets: + - x86_64-apple-darwin + - aarch64-apple-darwin env: - DEVELOPER_DIR: /Applications/Xcode_14.2.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer + PROTOC_PATH: /opt/homebrew/bin/protoc steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: - ssh-key: ${{ secrets.DEPLOY_KEY }} - submodules: recursive + fetch-depth: 0 - name: Import Certificate uses: ./.github/actions/import-cert with: certificate: ${{ secrets.DEVELOPER_CERT }} password: ${{ secrets.DEVELOPER_CERT_PASSWORD }} + - name: Download Provisioning Profiles + uses: ./.github/actions/download-profiles + with: + app-store-key: ${{ secrets.APPSTORE_KEY }} + app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} + app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} + - name: Install Provisioning Profiles + shell: bash + run: | + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles/ + cp -f Apple/Profiles/* ~/Library/MobileDevice/Provisioning\ Profiles/ + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ join(matrix.rust-targets, ', ') }} + - name: Install Protobuf + shell: bash + run: brew install protobuf + - name: Configure Version + id: version + shell: bash + run: echo "BUILD_NUMBER=$(Tools/version.sh)" >> $GITHUB_OUTPUT - name: Archive uses: ./.github/actions/archive with: - scheme: ${{ matrix.configuration['scheme'] }} - destination: ${{ matrix.configuration['destination'] }} + scheme: App + destination: generic/platform=${{ matrix.platform }} app-store-key: ${{ secrets.APPSTORE_KEY }} app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} archive-path: Burrow.xcarchive - - name: Export Locally + - name: Export uses: ./.github/actions/export with: - method: ${{ matrix.configuration['method'] }} + method: ${{ matrix.platform == 'macOS' && 'developer-id' || 'ad-hoc' }} destination: export app-store-key: ${{ secrets.APPSTORE_KEY }} app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} archive-path: Burrow.xcarchive + export-options: | + {"teamID":"P6PV2R9443","destination":"export","method":"developer-id","provisioningProfiles":{"com.hackclub.burrow":"Burrow Developer ID","com.hackclub.burrow.network":"Burrow Network Developer ID"},"signingCertificate":"Developer ID Application","signingStyle":"manual"} export-path: Release - - name: Compress - if: ${{ matrix.configuration['platform'] == 'macOS' }} + - name: Notarize + if: ${{ matrix.platform == 'macOS' }} + uses: ./.github/actions/notarize + with: + app-store-key: ${{ secrets.APPSTORE_KEY }} + app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} + app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} + - name: Compress (iOS) + if: ${{ matrix.platform == 'iOS' }} shell: bash - run: tar --options xz:compression-level=9 -C Apple/Release -cJf Burrow.app.txz ./ - - name: Attach Artifact - uses: SierraSoftworks/gh-releases@v1.0.6 + run: | + cp Apple/Release/Burrow.ipa Burrow.ipa + aa archive -a lzma -b 8m -d Apple -subdir Burrow.xcarchive -o Burrow-${{ matrix.platform }}.xcarchive.aar + rm -rf Apple/Release + - name: Compress (macOS) + if: ${{ matrix.platform == 'macOS' }} + shell: bash + run: | + aa archive -a lzma -b 8m -d Apple/Release -subdir Burrow.app -o Burrow.app.aar + aa archive -a lzma -b 8m -d Apple -subdir Burrow.xcarchive -o Burrow-${{ matrix.platform }}.xcarchive.aar + rm -rf Apple/Release + - name: Upload to GitHub + uses: SierraSoftworks/gh-releases@v1.0.7 with: token: ${{ secrets.GITHUB_TOKEN }} - overwrite: 'false' - files: ${{ matrix.configuration['artifact-file'] }} + release_tag: ${{ github.ref_name }} + overwrite: 'true' + files: | + ${{ matrix.platform == 'macOS' && 'Burrow.aap.aar' || 'Burrow.ipa' }} + Burrow-${{ matrix.platform }}.xcarchive.aar + - name: Upload to App Store Connect + if: ${{ matrix.platform == 'iOS' }} + uses: ./.github/actions/export + with: + app-store-key: ${{ secrets.APPSTORE_KEY }} + app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }} + app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }} + archive-path: Burrow.xcarchive + export-options: | + {"method": "app-store", "destination": "upload"} + export-path: Release diff --git a/.github/workflows/release-if-needed.yaml b/.github/workflows/release-if-needed.yaml new file mode 100644 index 0000000..79f0d63 --- /dev/null +++ b/.github/workflows/release-if-needed.yaml @@ -0,0 +1,23 @@ +name: Create Release If Needed +on: + workflow_dispatch: + schedule: + - cron: '0 10 * * *' +concurrency: + group: ${{ github.workflow }} +jobs: + create: + name: Create Release If Needed + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ github.token }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - shell: bash + run: | + if [[ $(Tools/version.sh status) == "dirty" ]]; then + gh workflow run release-now.yml + fi diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml new file mode 100644 index 0000000..7db9bcf --- /dev/null +++ b/.github/workflows/release-linux.yml @@ -0,0 +1,29 @@ +name: Release (Linux) +on: + release: + types: + - created +jobs: + appimage: + name: Build AppImage + runs-on: ubuntu-latest + container: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Build AppImage + run: | + docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile + docker create --name temp appimage-builder + docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage . + docker rm temp + - name: Attach Artifacts + uses: SierraSoftworks/gh-releases@v1.0.7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + release_tag: ${{ github.ref_name }} + overwrite: "true" + files: | + Burrow-x86_64.AppImage diff --git a/.github/workflows/release-now.yml b/.github/workflows/release-now.yml new file mode 100644 index 0000000..229f6c9 --- /dev/null +++ b/.github/workflows/release-now.yml @@ -0,0 +1,17 @@ +name: Create Release +on: workflow_dispatch +concurrency: + group: ${{ github.workflow }} +jobs: + create: + env: + GH_TOKEN: ${{ secrets.GH_RELEASE_TOKEN }} + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - shell: bash + run: Tools/version.sh increment diff --git a/.gitignore b/.gitignore index 102ee0d..1b300b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,17 @@ # Xcode xcuserdata +# Swift +Apple/Package/.swiftpm/ + # Rust target/ +.env + +.DS_STORE +.idea/ + +tmp/ + +*.db +*.sock \ No newline at end of file diff --git a/.gitlint b/.gitlint index b185b1c..a72a004 100644 --- a/.gitlint +++ b/.gitlint @@ -1,5 +1,5 @@ [general] -ignore=body-changed-file-mention +ignore=body-changed-file-mention,body-is-missing ignore-merge-commits=false ignore-fixup-commits=false ignore-squash-commits=false diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..2a12e19 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,12 @@ +condense_wildcard_suffixes = true +format_macro_matchers = true +imports_layout = "HorizontalVertical" +imports_granularity = "Crate" +newline_style = "Unix" +overflow_delimited_expr = true +reorder_impl_items = true +group_imports = "StdExternalCrate" +trailing_semicolon = false +use_field_init_shorthand = true +use_try_shorthand = true +struct_lit_width = 30 diff --git a/.swiftlint.yml b/.swiftlint.yml index d609718..8efc85e 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -30,7 +30,6 @@ opt_in_rules: - function_default_parameter_at_end - ibinspectable_in_extension - identical_operands -- implicitly_unwrapped_optional - indentation_width - joined_default_parameter - last_where @@ -46,7 +45,6 @@ opt_in_rules: - multiline_parameters - multiline_parameters_brackets - no_extension_access_modifier -- no_grouping_extension - nslocalizedstring_key - nslocalizedstring_require_bundle - number_separator @@ -76,9 +74,7 @@ opt_in_rules: - sorted_first_last - sorted_imports - static_operator -- strict_fileprivate - strong_iboutlet -- switch_case_on_newline - test_case_accessibility - toggle_bool - trailing_closure @@ -97,3 +93,5 @@ disabled_rules: - force_try - nesting - todo +- trailing_comma +- switch_case_on_newline diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c5c0dd4..e40eae1 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ "rust-lang.rust-analyzer", + "vadimcn.vscode-lldb", ] -} +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 287ffdd..eb85504 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,16 +1,26 @@ { - "files.autoSave": "onFocusChange", - "files.defaultLanguage": "rust", - "explorer.excludeGitIgnore": true, - "editor.formatOnPaste": true, - "editor.formatOnSave": true, - "files.trimTrailingWhitespace": true, - "editor.suggest.preview": true, - "editor.acceptSuggestionOnEnter": "on", - "rust-analyzer.checkOnSave.command": "clippy", - "rust-analyzer.restartServerOnConfigChange": true, - "rust-analyzer.cargo.features": "all", - "[rust]": { - "editor.defaultFormatter": "rust-lang.rust-analyzer", - }, + "files.autoSave": "onFocusChange", + "files.defaultLanguage": "rust", + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "files.trimTrailingWhitespace": true, + "editor.suggest.preview": true, + "editor.acceptSuggestionOnEnter": "on", + "rust-analyzer.restartServerOnConfigChange": true, + "rust-analyzer.cargo.features": "all", + "rust-analyzer.rustfmt.extraArgs": ["+nightly"], + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" + }, + "rust-analyzer.inlayHints.typeHints.enable": false, + "rust-analyzer.linkedProjects": [ + "./burrow/Cargo.toml" + ], + "[yaml]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.autoIndent": "advanced", + "diffEditor.ignoreTrimWhitespace": false, + "editor.formatOnSave": false + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index eb2a68a..3eb24be 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,7 +3,7 @@ "tasks": [ { "type": "cargo", - "command": "check", + "command": "clippy", "problemMatcher": [ "$rustc" ], @@ -11,7 +11,13 @@ "kind": "build", "isDefault": true }, - "label": "Check" - } + "label": "rust: cargo clippy", + "args": [ + "--workspace", + "--all-targets", + "--all-features", + "--all" + ] + }, ] } \ No newline at end of file diff --git a/Apple/App/App-iOS.entitlements b/Apple/App/App-iOS.entitlements index d9849a8..53fcbb7 100644 --- a/Apple/App/App-iOS.entitlements +++ b/Apple/App/App-iOS.entitlements @@ -2,6 +2,15 @@ + com.apple.developer.associated-domains + + applinks:burrow.rs?mode=developer + webcredentials:burrow.rs?mode=developer + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + com.apple.security.application-groups $(APP_GROUP_IDENTIFIER) diff --git a/Apple/App/App-macOS.entitlements b/Apple/App/App-macOS.entitlements index d9849a8..53fcbb7 100644 --- a/Apple/App/App-macOS.entitlements +++ b/Apple/App/App-macOS.entitlements @@ -2,6 +2,15 @@ + com.apple.developer.associated-domains + + applinks:burrow.rs?mode=developer + webcredentials:burrow.rs?mode=developer + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + com.apple.security.application-groups $(APP_GROUP_IDENTIFIER) diff --git a/Apple/App/Burrow.xcconfig b/Apple/App/App.xcconfig similarity index 67% rename from Apple/App/Burrow.xcconfig rename to Apple/App/App.xcconfig index a4922de..4e42ddc 100644 --- a/Apple/App/Burrow.xcconfig +++ b/Apple/App/App.xcconfig @@ -2,6 +2,7 @@ PRODUCT_NAME = Burrow PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER) +PRODUCT_MODULE_NAME = BurrowApp INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphone*] = YES INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphone*] = YES @@ -10,8 +11,13 @@ INFOPLIST_KEY_UIStatusBarStyle[sdk=iphone*] = UIStatusBarStyleDefault INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad[sdk=iphone*] = UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone[sdk=iphone*] = UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2 +EXCLUDED_SOURCE_FILE_NAMES = MainMenu.xib -INFOPLIST_KEY_LSApplicationCategoryType[sdk=macos*] = public.app-category.utilities +EXCLUDED_SOURCE_FILE_NAMES[sdk=macosx*] = +INFOPLIST_KEY_LSUIElement[sdk=macosx*] = YES +INFOPLIST_KEY_NSMainNibFile[sdk=macosx*] = MainMenu +INFOPLIST_KEY_NSPrincipalClass[sdk=macosx*] = NSApplication +INFOPLIST_KEY_LSApplicationCategoryType[sdk=macosx*] = public.app-category.utilities CODE_SIGN_ENTITLEMENTS = App/App-iOS.entitlements -CODE_SIGN_ENTITLEMENTS[sdk=macos*] = App/App-macOS.entitlements +CODE_SIGN_ENTITLEMENTS[sdk=macosx*] = App/App-macOS.entitlements diff --git a/Apple/App/AppDelegate.swift b/Apple/App/AppDelegate.swift new file mode 100644 index 0000000..0ea93f4 --- /dev/null +++ b/Apple/App/AppDelegate.swift @@ -0,0 +1,53 @@ +#if os(macOS) +import AppKit +import BurrowUI +import SwiftUI + +@main +@MainActor +class AppDelegate: NSObject, NSApplicationDelegate { + private let quitItem: NSMenuItem = { + let quitItem = NSMenuItem( + title: "Quit Burrow", + action: #selector(NSApplication.terminate(_:)), + keyEquivalent: "q" + ) + quitItem.target = NSApplication.shared + quitItem.keyEquivalentModifierMask = .command + return quitItem + }() + + private let toggleItem: NSMenuItem = { + let toggleView = NSHostingView(rootView: MenuItemToggleView()) + toggleView.frame.size = CGSize(width: 300, height: 32) + toggleView.autoresizingMask = [.width] + + let toggleItem = NSMenuItem() + toggleItem.view = toggleView + return toggleItem + }() + + private lazy var menu: NSMenu = { + let menu = NSMenu() + menu.items = [ + toggleItem, + .separator(), + quitItem + ] + return menu + }() + + private lazy var statusItem: NSStatusItem = { + let statusBar = NSStatusBar.system + let statusItem = statusBar.statusItem(withLength: NSStatusItem.squareLength) + if let button = statusItem.button { + button.image = NSImage(systemSymbolName: "network.badge.shield.half.filled", accessibilityDescription: nil) + } + return statusItem + }() + + func applicationDidFinishLaunching(_ notification: Notification) { + statusItem.menu = menu + } +} +#endif diff --git a/Apple/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Apple/App/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 532cd72..0000000 --- a/Apple/App/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Apple/App/BurrowApp.swift b/Apple/App/BurrowApp.swift index b145dea..838ef54 100644 --- a/Apple/App/BurrowApp.swift +++ b/Apple/App/BurrowApp.swift @@ -1,10 +1,14 @@ +#if !os(macOS) +import BurrowUI import SwiftUI +@MainActor @main struct BurrowApp: App { var body: some Scene { WindowGroup { - ContentView() + BurrowView() } } } +#endif diff --git a/Apple/App/ContentView.swift b/Apple/App/ContentView.swift deleted file mode 100644 index f54deab..0000000 --- a/Apple/App/ContentView.swift +++ /dev/null @@ -1,19 +0,0 @@ -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundColor(.accentColor) - Text("Hello, world!") - } - .padding() - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/Apple/App/MainMenu.xib b/Apple/App/MainMenu.xib new file mode 100644 index 0000000..50ba431 --- /dev/null +++ b/Apple/App/MainMenu.xib @@ -0,0 +1,679 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj index fa4009a..617b88f 100644 --- a/Apple/Burrow.xcodeproj/project.pbxproj +++ b/Apple/Burrow.xcodeproj/project.pbxproj @@ -7,11 +7,47 @@ objects = { /* Begin PBXBuildFile section */ + D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; }; D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; }; D020F65D29E4A697002790F6 /* BurrowNetworkExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + D03383AD2C8E67E300F7C44E /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = D078F7E22C8DA375008A8CEC /* SwiftProtobuf */; }; + D03383AE2C8E67E300F7C44E /* NIO in Frameworks */ = {isa = PBXBuildFile; productRef = D044EE902C8DAB2000778185 /* NIO */; }; + D03383AF2C8E67E300F7C44E /* NIOConcurrencyHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = D044EE922C8DAB2000778185 /* NIOConcurrencyHelpers */; }; + D03383B02C8E67E300F7C44E /* NIOTransportServices in Frameworks */ = {isa = PBXBuildFile; productRef = D044EE952C8DAB2800778185 /* NIOTransportServices */; }; D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */; }; - D05B9F7829E39EEC008CB1F9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7729E39EEC008CB1F9 /* ContentView.swift */; }; - D05B9F7A29E39EED008CB1F9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D05B9F7929E39EED008CB1F9 /* Assets.xcassets */; }; + D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D09150412B9D2AF700BE3CB0 /* MainMenu.xib */; platformFilters = (macos, ); }; + D0B1D1102C436152004B7823 /* AsyncAlgorithms in Frameworks */ = {isa = PBXBuildFile; productRef = D0B1D10F2C436152004B7823 /* AsyncAlgorithms */; }; + D0BCC6092A09A03E00AD070D /* libburrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BCC6032A09535900AD070D /* libburrow.a */; }; + D0BF09522C8E66F6000D8DEC /* BurrowConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; }; + D0BF09552C8E66FD000D8DEC /* BurrowConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; }; + D0D4E53A2C8D996F007F820A /* BurrowCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D0D4E56B2C8D9C2F007F820A /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49A2C8D921A007F820A /* Logging.swift */; }; + D0D4E5702C8D9C62007F820A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; }; + D0D4E5712C8D9C6F007F820A /* HackClub.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49D2C8D921A007F820A /* HackClub.swift */; }; + D0D4E5722C8D9C6F007F820A /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49E2C8D921A007F820A /* Network.swift */; }; + D0D4E5732C8D9C6F007F820A /* WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E49F2C8D921A007F820A /* WireGuard.swift */; }; + D0D4E5742C8D9C6F007F820A /* BurrowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A22C8D921A007F820A /* BurrowView.swift */; }; + D0D4E5752C8D9C6F007F820A /* FloatingButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A32C8D921A007F820A /* FloatingButtonStyle.swift */; }; + D0D4E5762C8D9C6F007F820A /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A42C8D921A007F820A /* MenuItemToggleView.swift */; }; + D0D4E5772C8D9C6F007F820A /* NetworkCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A52C8D921A007F820A /* NetworkCarouselView.swift */; }; + D0D4E5782C8D9C6F007F820A /* NetworkExtension+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A62C8D921A007F820A /* NetworkExtension+Async.swift */; }; + D0D4E5792C8D9C6F007F820A /* NetworkExtensionTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A72C8D921A007F820A /* NetworkExtensionTunnel.swift */; }; + D0D4E57A2C8D9C6F007F820A /* NetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A82C8D921A007F820A /* NetworkView.swift */; }; + D0D4E57B2C8D9C6F007F820A /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4A92C8D921A007F820A /* OAuth2.swift */; }; + D0D4E57C2C8D9C6F007F820A /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4AA2C8D921A007F820A /* Tunnel.swift */; }; + D0D4E57D2C8D9C6F007F820A /* TunnelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4AB2C8D921A007F820A /* TunnelButton.swift */; }; + D0D4E57E2C8D9C6F007F820A /* TunnelStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4AC2C8D921A007F820A /* TunnelStatusView.swift */; }; + D0D4E5892C8D9C94007F820A /* BurrowUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */; }; + D0D4E58A2C8D9C9E007F820A /* BurrowUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D0D4E58B2C8D9CA4007F820A /* BurrowConfiguration.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D0D4E5922C8D9D15007F820A /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E58F2C8D9D0A007F820A /* Constants.swift */; }; + D0D4E5A62C8D9E65007F820A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; }; + D0F4FAD32C8DC79C0068730A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; }; + D0F7594E2C8DAB6B00126CF3 /* GRPC in Frameworks */ = {isa = PBXBuildFile; productRef = D078F7E02C8DA375008A8CEC /* GRPC */; }; + D0F759612C8DB24B00126CF3 /* grpc-swift-config.json in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4962C8D921A007F820A /* grpc-swift-config.json */; }; + D0F759622C8DB24B00126CF3 /* swift-protobuf-config.json in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4972C8D921A007F820A /* swift-protobuf-config.json */; }; + D0F7597E2C8DB30500126CF3 /* CGRPCZlib in Frameworks */ = {isa = PBXBuildFile; productRef = D0F7597D2C8DB30500126CF3 /* CGRPCZlib */; }; + D0F7598D2C8DB3DA00126CF3 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4992C8D921A007F820A /* Client.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -22,6 +58,48 @@ remoteGlobalIDString = D020F65229E4A697002790F6; remoteInfo = BurrowNetworkExtension; }; + D0BF09502C8E66F1000D8DEC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E55A2C8D9BF4007F820A; + remoteInfo = Configuration; + }; + D0BF09532C8E66FA000D8DEC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E55A2C8D9BF4007F820A; + remoteInfo = Configuration; + }; + D0D4E56E2C8D9C5D007F820A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E5302C8D996F007F820A; + remoteInfo = Core; + }; + D0D4E57F2C8D9C78007F820A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E5302C8D996F007F820A; + remoteInfo = Core; + }; + D0D4E5872C8D9C88007F820A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E5502C8D9BF2007F820A; + remoteInfo = UI; + }; + D0F4FAD12C8DC7960068730A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0D4E5302C8D996F007F820A; + remoteInfo = Core; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -36,13 +114,28 @@ name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + D0D4E53F2C8D996F007F820A /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D0D4E58B2C8D9CA4007F820A /* BurrowConfiguration.framework in Embed Frameworks */, + D0D4E58A2C8D9C9E007F820A /* BurrowUI.framework in Embed Frameworks */, + D0D4E53A2C8D996F007F820A /* BurrowCore.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + D00117422B30348D00D87C25 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Configuration.xcconfig; sourceTree = ""; }; + D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = ""; }; D020F64029E4A1FF002790F6 /* Compiler.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = ""; }; D020F64229E4A1FF002790F6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D020F64929E4A34B002790F6 /* Burrow.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Burrow.xcconfig; sourceTree = ""; }; + D020F64929E4A34B002790F6 /* App.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = App.xcconfig; sourceTree = ""; }; D020F64A29E4A452002790F6 /* App.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = App.xcconfig; sourceTree = ""; }; D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = BurrowNetworkExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; @@ -53,10 +146,43 @@ D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NetworkExtension-iOS.entitlements"; sourceTree = ""; }; D020F66829E4AA74002790F6 /* App-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "App-iOS.entitlements"; sourceTree = ""; }; D020F66929E4AA74002790F6 /* App-macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "App-macOS.entitlements"; sourceTree = ""; }; + D04A3E1D2BAF465F0043EC85 /* Version.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.xcconfig; sourceTree = ""; }; D05B9F7229E39EEC008CB1F9 /* Burrow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Burrow.app; sourceTree = BUILT_PRODUCTS_DIR; }; D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowApp.swift; sourceTree = ""; }; - D05B9F7729E39EEC008CB1F9 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - D05B9F7929E39EED008CB1F9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + D09150412B9D2AF700BE3CB0 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; + D0B98FBF29FD8072004E7149 /* build-rust.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "build-rust.sh"; sourceTree = ""; }; + D0B98FD829FDDB6F004E7149 /* libburrow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libburrow.h; sourceTree = ""; }; + D0B98FDC29FDDDCF004E7149 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + D0BCC6032A09535900AD070D /* libburrow.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libburrow.a; sourceTree = BUILT_PRODUCTS_DIR; }; + D0BF09582C8E6789000D8DEC /* UI.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UI.xcconfig; sourceTree = ""; }; + D0D4E4952C8D921A007F820A /* burrow.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = burrow.proto; sourceTree = ""; }; + D0D4E4962C8D921A007F820A /* grpc-swift-config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "grpc-swift-config.json"; sourceTree = ""; }; + D0D4E4972C8D921A007F820A /* swift-protobuf-config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "swift-protobuf-config.json"; sourceTree = ""; }; + D0D4E4992C8D921A007F820A /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; + D0D4E49A2C8D921A007F820A /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + D0D4E49D2C8D921A007F820A /* HackClub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HackClub.swift; sourceTree = ""; }; + D0D4E49E2C8D921A007F820A /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; + D0D4E49F2C8D921A007F820A /* WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuard.swift; sourceTree = ""; }; + D0D4E4A12C8D921A007F820A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + D0D4E4A22C8D921A007F820A /* BurrowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowView.swift; sourceTree = ""; }; + D0D4E4A32C8D921A007F820A /* FloatingButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingButtonStyle.swift; sourceTree = ""; }; + D0D4E4A42C8D921A007F820A /* MenuItemToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemToggleView.swift; sourceTree = ""; }; + D0D4E4A52C8D921A007F820A /* NetworkCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkCarouselView.swift; sourceTree = ""; }; + D0D4E4A62C8D921A007F820A /* NetworkExtension+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkExtension+Async.swift"; sourceTree = ""; }; + D0D4E4A72C8D921A007F820A /* NetworkExtensionTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkExtensionTunnel.swift; sourceTree = ""; }; + D0D4E4A82C8D921A007F820A /* NetworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkView.swift; sourceTree = ""; }; + D0D4E4A92C8D921A007F820A /* OAuth2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2.swift; sourceTree = ""; }; + D0D4E4AA2C8D921A007F820A /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = ""; }; + D0D4E4AB2C8D921A007F820A /* TunnelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelButton.swift; sourceTree = ""; }; + D0D4E4AC2C8D921A007F820A /* TunnelStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusView.swift; sourceTree = ""; }; + D0D4E4F62C8D932D007F820A /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + D0D4E4F72C8D941D007F820A /* Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Framework.xcconfig; sourceTree = ""; }; + D0D4E5312C8D996F007F820A /* BurrowCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BurrowCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BurrowUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BurrowConfiguration.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0D4E58E2C8D9D0A007F820A /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; + D0D4E58F2C8D9D0A007F820A /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + D0D4E5902C8D9D0A007F820A /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,6 +190,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D0BF09522C8E66F6000D8DEC /* BurrowConfiguration.framework in Frameworks */, + D0D4E5A62C8D9E65007F820A /* BurrowCore.framework in Frameworks */, + D0BCC6092A09A03E00AD070D /* libburrow.a in Frameworks */, + D0B1D1102C436152004B7823 /* AsyncAlgorithms in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -71,20 +201,56 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D0BF09552C8E66FD000D8DEC /* BurrowConfiguration.framework in Frameworks */, + D0F4FAD32C8DC79C0068730A /* BurrowCore.framework in Frameworks */, + D0D4E5892C8D9C94007F820A /* BurrowUI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D078F7CF2C8DA213008A8CEC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D03383B02C8E67E300F7C44E /* NIOTransportServices in Frameworks */, + D03383AF2C8E67E300F7C44E /* NIOConcurrencyHelpers in Frameworks */, + D03383AE2C8E67E300F7C44E /* NIO in Frameworks */, + D03383AD2C8E67E300F7C44E /* SwiftProtobuf in Frameworks */, + D0F7594E2C8DAB6B00126CF3 /* GRPC in Frameworks */, + D0F7597E2C8DB30500126CF3 /* CGRPCZlib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0D4E5532C8D9BF2007F820A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D0D4E5702C8D9C62007F820A /* BurrowCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + D00117432B30372900D87C25 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; D020F63C29E4A1FF002790F6 /* Configuration */ = { isa = PBXGroup; children = ( D020F63D29E4A1FF002790F6 /* Identity.xcconfig */, D020F64A29E4A452002790F6 /* App.xcconfig */, D020F66329E4A703002790F6 /* Extension.xcconfig */, + D0D4E4F72C8D941D007F820A /* Framework.xcconfig */, D020F64029E4A1FF002790F6 /* Compiler.xcconfig */, + D0D4E4F62C8D932D007F820A /* Debug.xcconfig */, + D04A3E1D2BAF465F0043EC85 /* Version.xcconfig */, D020F64229E4A1FF002790F6 /* Info.plist */, + D0D4E5912C8D9D0A007F820A /* Constants */, + D00117422B30348D00D87C25 /* Configuration.xcconfig */, ); path = Configuration; sourceTree = ""; @@ -97,6 +263,7 @@ D020F66729E4A95D002790F6 /* NetworkExtension-iOS.entitlements */, D020F66629E4A95D002790F6 /* NetworkExtension-macOS.entitlements */, D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */, + D0B98FD729FDDB57004E7149 /* libburrow */, ); path = NetworkExtension; sourceTree = ""; @@ -106,8 +273,11 @@ children = ( D05B9F7429E39EEC008CB1F9 /* App */, D020F65629E4A697002790F6 /* NetworkExtension */, + D0D4E49C2C8D921A007F820A /* Core */, + D0D4E4AD2C8D921A007F820A /* UI */, D020F63C29E4A1FF002790F6 /* Configuration */, D05B9F7329E39EEC008CB1F9 /* Products */, + D00117432B30372900D87C25 /* Frameworks */, ); sourceTree = ""; }; @@ -116,6 +286,10 @@ children = ( D05B9F7229E39EEC008CB1F9 /* Burrow.app */, D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */, + D0BCC6032A09535900AD070D /* libburrow.a */, + D0D4E5312C8D996F007F820A /* BurrowCore.framework */, + D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */, + D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */, ); name = Products; sourceTree = ""; @@ -124,15 +298,86 @@ isa = PBXGroup; children = ( D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */, - D05B9F7729E39EEC008CB1F9 /* ContentView.swift */, - D05B9F7929E39EED008CB1F9 /* Assets.xcassets */, + D00AA8962A4669BC005C8102 /* AppDelegate.swift */, + D09150412B9D2AF700BE3CB0 /* MainMenu.xib */, D020F66829E4AA74002790F6 /* App-iOS.entitlements */, D020F66929E4AA74002790F6 /* App-macOS.entitlements */, - D020F64929E4A34B002790F6 /* Burrow.xcconfig */, + D020F64929E4A34B002790F6 /* App.xcconfig */, ); path = App; sourceTree = ""; }; + D0B98FD729FDDB57004E7149 /* libburrow */ = { + isa = PBXGroup; + children = ( + D0B98FBF29FD8072004E7149 /* build-rust.sh */, + D0B98FDC29FDDDCF004E7149 /* module.modulemap */, + D0B98FD829FDDB6F004E7149 /* libburrow.h */, + ); + path = libburrow; + sourceTree = ""; + }; + D0D4E4982C8D921A007F820A /* Client */ = { + isa = PBXGroup; + children = ( + D0D4E4952C8D921A007F820A /* burrow.proto */, + D0D4E4962C8D921A007F820A /* grpc-swift-config.json */, + D0D4E4972C8D921A007F820A /* swift-protobuf-config.json */, + ); + path = Client; + sourceTree = ""; + }; + D0D4E49C2C8D921A007F820A /* Core */ = { + isa = PBXGroup; + children = ( + D0D4E49A2C8D921A007F820A /* Logging.swift */, + D0D4E4992C8D921A007F820A /* Client.swift */, + D0D4E4982C8D921A007F820A /* Client */, + ); + path = Core; + sourceTree = ""; + }; + D0D4E4A02C8D921A007F820A /* Networks */ = { + isa = PBXGroup; + children = ( + D0D4E49D2C8D921A007F820A /* HackClub.swift */, + D0D4E49E2C8D921A007F820A /* Network.swift */, + D0D4E49F2C8D921A007F820A /* WireGuard.swift */, + ); + path = Networks; + sourceTree = ""; + }; + D0D4E4AD2C8D921A007F820A /* UI */ = { + isa = PBXGroup; + children = ( + D0D4E4A22C8D921A007F820A /* BurrowView.swift */, + D0D4E4A02C8D921A007F820A /* Networks */, + D0D4E4A32C8D921A007F820A /* FloatingButtonStyle.swift */, + D0D4E4A42C8D921A007F820A /* MenuItemToggleView.swift */, + D0D4E4A52C8D921A007F820A /* NetworkCarouselView.swift */, + D0D4E4A62C8D921A007F820A /* NetworkExtension+Async.swift */, + D0D4E4A72C8D921A007F820A /* NetworkExtensionTunnel.swift */, + D0D4E4A82C8D921A007F820A /* NetworkView.swift */, + D0D4E4A92C8D921A007F820A /* OAuth2.swift */, + D0D4E4AA2C8D921A007F820A /* Tunnel.swift */, + D0D4E4AB2C8D921A007F820A /* TunnelButton.swift */, + D0D4E4AC2C8D921A007F820A /* TunnelStatusView.swift */, + D0D4E4A12C8D921A007F820A /* Assets.xcassets */, + D0BF09582C8E6789000D8DEC /* UI.xcconfig */, + ); + path = UI; + sourceTree = ""; + }; + D0D4E5912C8D9D0A007F820A /* Constants */ = { + isa = PBXGroup; + children = ( + D0D4E58E2C8D9D0A007F820A /* Constants.h */, + D0D4E58F2C8D9D0A007F820A /* Constants.swift */, + D0D4E5902C8D9D0A007F820A /* module.modulemap */, + ); + path = Constants; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -140,38 +385,109 @@ isa = PBXNativeTarget; buildConfigurationList = D020F65E29E4A697002790F6 /* Build configuration list for PBXNativeTarget "NetworkExtension" */; buildPhases = ( + D0BCC60B2A09A0C100AD070D /* Compile Rust */, D020F64F29E4A697002790F6 /* Sources */, D020F65029E4A697002790F6 /* Frameworks */, - D020F65129E4A697002790F6 /* Resources */, ); buildRules = ( ); dependencies = ( + D0BF09512C8E66F1000D8DEC /* PBXTargetDependency */, + D0D4E5802C8D9C78007F820A /* PBXTargetDependency */, ); name = NetworkExtension; productName = BurrowNetworkExtension; productReference = D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */; productType = "com.apple.product-type.app-extension"; }; - D05B9F7129E39EEC008CB1F9 /* Burrow */ = { + D05B9F7129E39EEC008CB1F9 /* App */ = { isa = PBXNativeTarget; - buildConfigurationList = D05B9F8129E39EED008CB1F9 /* Build configuration list for PBXNativeTarget "Burrow" */; + buildConfigurationList = D05B9F8129E39EED008CB1F9 /* Build configuration list for PBXNativeTarget "App" */; buildPhases = ( D05B9F6E29E39EEC008CB1F9 /* Sources */, D05B9F6F29E39EEC008CB1F9 /* Frameworks */, D05B9F7029E39EEC008CB1F9 /* Resources */, + D0D4E53F2C8D996F007F820A /* Embed Frameworks */, D020F66129E4A697002790F6 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + D0BF09542C8E66FA000D8DEC /* PBXTargetDependency */, + D0F4FAD22C8DC7960068730A /* PBXTargetDependency */, + D0D4E5882C8D9C88007F820A /* PBXTargetDependency */, D020F65C29E4A697002790F6 /* PBXTargetDependency */, ); - name = Burrow; + name = App; productName = Burrow; productReference = D05B9F7229E39EEC008CB1F9 /* Burrow.app */; productType = "com.apple.product-type.application"; }; + D0D4E5302C8D996F007F820A /* Core */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0D4E53C2C8D996F007F820A /* Build configuration list for PBXNativeTarget "Core" */; + buildPhases = ( + D0D4E52D2C8D996F007F820A /* Sources */, + D078F7CF2C8DA213008A8CEC /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D0F7598A2C8DB34200126CF3 /* PBXTargetDependency */, + D0F7595E2C8DB24400126CF3 /* PBXTargetDependency */, + D0F759602C8DB24400126CF3 /* PBXTargetDependency */, + ); + name = Core; + packageProductDependencies = ( + D078F7E02C8DA375008A8CEC /* GRPC */, + D078F7E22C8DA375008A8CEC /* SwiftProtobuf */, + D044EE902C8DAB2000778185 /* NIO */, + D044EE922C8DAB2000778185 /* NIOConcurrencyHelpers */, + D044EE952C8DAB2800778185 /* NIOTransportServices */, + D0F7597D2C8DB30500126CF3 /* CGRPCZlib */, + ); + productName = Core; + productReference = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; + productType = "com.apple.product-type.framework"; + }; + D0D4E5502C8D9BF2007F820A /* UI */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0D4E5552C8D9BF2007F820A /* Build configuration list for PBXNativeTarget "UI" */; + buildPhases = ( + D0D4E5522C8D9BF2007F820A /* Sources */, + D0D4E5532C8D9BF2007F820A /* Frameworks */, + D0D4E5542C8D9BF2007F820A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D0D4E56F2C8D9C5D007F820A /* PBXTargetDependency */, + ); + name = UI; + packageProductDependencies = ( + ); + productName = Core; + productReference = D0D4E5582C8D9BF2007F820A /* BurrowUI.framework */; + productType = "com.apple.product-type.framework"; + }; + D0D4E55A2C8D9BF4007F820A /* Configuration */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0D4E55F2C8D9BF4007F820A /* Build configuration list for PBXNativeTarget "Configuration" */; + buildPhases = ( + D0F759912C8DB49E00126CF3 /* Configure Version */, + D0D4E55C2C8D9BF4007F820A /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Configuration; + packageProductDependencies = ( + ); + productName = Core; + productReference = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; + productType = "com.apple.product-type.framework"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -179,8 +495,8 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1430; - LastUpgradeCheck = 1430; + LastSwiftUpdateCheck = 1600; + LastUpgradeCheck = 1520; TargetAttributes = { D020F65229E4A697002790F6 = { CreatedOnToolsVersion = 14.3; @@ -188,6 +504,9 @@ D05B9F7129E39EEC008CB1F9 = { CreatedOnToolsVersion = 14.3; }; + D0D4E5302C8D996F007F820A = { + CreatedOnToolsVersion = 16.0; + }; }; }; buildConfigurationList = D05B9F6D29E39EEC008CB1F9 /* Build configuration list for PBXProject "Burrow" */; @@ -199,34 +518,89 @@ Base, ); mainGroup = D05B9F6929E39EEC008CB1F9; + packageReferences = ( + D0B1D10E2C436152004B7823 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */, + D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */, + D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */, + D044EE8F2C8DAB2000778185 /* XCRemoteSwiftPackageReference "swift-nio" */, + D044EE942C8DAB2800778185 /* XCRemoteSwiftPackageReference "swift-nio-transport-services" */, + ); productRefGroup = D05B9F7329E39EEC008CB1F9 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - D05B9F7129E39EEC008CB1F9 /* Burrow */, + D05B9F7129E39EEC008CB1F9 /* App */, D020F65229E4A697002790F6 /* NetworkExtension */, + D0D4E5502C8D9BF2007F820A /* UI */, + D0D4E5302C8D996F007F820A /* Core */, + D0D4E55A2C8D9BF4007F820A /* Configuration */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - D020F65129E4A697002790F6 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; D05B9F7029E39EEC008CB1F9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D05B9F7A29E39EED008CB1F9 /* Assets.xcassets in Resources */, + D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0D4E5542C8D9BF2007F820A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + D0BCC60B2A09A0C100AD070D /* Compile Rust */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Compile Rust"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PROJECT_DIR}/NetworkExtension/libburrow/build-rust.sh\"\n"; + showEnvVarsInLog = 0; + }; + D0F759912C8DB49E00126CF3 /* Configure Version */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(PROJECT_DIR)/../Tools/version.sh", + "$(PROJECT_DIR)/../.git", + ); + name = "Configure Version"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(PROJECT_DIR)/Configuration/Version.xcconfig", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$PROJECT_DIR/../Tools/version.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ D020F64F29E4A697002790F6 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -240,11 +614,51 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D05B9F7829E39EEC008CB1F9 /* ContentView.swift in Sources */, + D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */, D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + D0D4E52D2C8D996F007F820A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0F759612C8DB24B00126CF3 /* grpc-swift-config.json in Sources */, + D0F759622C8DB24B00126CF3 /* swift-protobuf-config.json in Sources */, + D0F7598D2C8DB3DA00126CF3 /* Client.swift in Sources */, + D0D4E56B2C8D9C2F007F820A /* Logging.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0D4E5522C8D9BF2007F820A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0D4E5712C8D9C6F007F820A /* HackClub.swift in Sources */, + D0D4E5722C8D9C6F007F820A /* Network.swift in Sources */, + D0D4E5732C8D9C6F007F820A /* WireGuard.swift in Sources */, + D0D4E5742C8D9C6F007F820A /* BurrowView.swift in Sources */, + D0D4E5752C8D9C6F007F820A /* FloatingButtonStyle.swift in Sources */, + D0D4E5762C8D9C6F007F820A /* MenuItemToggleView.swift in Sources */, + D0D4E5772C8D9C6F007F820A /* NetworkCarouselView.swift in Sources */, + D0D4E5782C8D9C6F007F820A /* NetworkExtension+Async.swift in Sources */, + D0D4E5792C8D9C6F007F820A /* NetworkExtensionTunnel.swift in Sources */, + D0D4E57A2C8D9C6F007F820A /* NetworkView.swift in Sources */, + D0D4E57B2C8D9C6F007F820A /* OAuth2.swift in Sources */, + D0D4E57C2C8D9C6F007F820A /* Tunnel.swift in Sources */, + D0D4E57D2C8D9C6F007F820A /* TunnelButton.swift in Sources */, + D0D4E57E2C8D9C6F007F820A /* TunnelStatusView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0D4E55C2C8D9BF4007F820A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0D4E5922C8D9D15007F820A /* Constants.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -253,6 +667,48 @@ target = D020F65229E4A697002790F6 /* NetworkExtension */; targetProxy = D020F65B29E4A697002790F6 /* PBXContainerItemProxy */; }; + D0BF09512C8E66F1000D8DEC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E55A2C8D9BF4007F820A /* Configuration */; + targetProxy = D0BF09502C8E66F1000D8DEC /* PBXContainerItemProxy */; + }; + D0BF09542C8E66FA000D8DEC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E55A2C8D9BF4007F820A /* Configuration */; + targetProxy = D0BF09532C8E66FA000D8DEC /* PBXContainerItemProxy */; + }; + D0D4E56F2C8D9C5D007F820A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E5302C8D996F007F820A /* Core */; + targetProxy = D0D4E56E2C8D9C5D007F820A /* PBXContainerItemProxy */; + }; + D0D4E5802C8D9C78007F820A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E5302C8D996F007F820A /* Core */; + targetProxy = D0D4E57F2C8D9C78007F820A /* PBXContainerItemProxy */; + }; + D0D4E5882C8D9C88007F820A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E5502C8D9BF2007F820A /* UI */; + targetProxy = D0D4E5872C8D9C88007F820A /* PBXContainerItemProxy */; + }; + D0F4FAD22C8DC7960068730A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0D4E5302C8D996F007F820A /* Core */; + targetProxy = D0F4FAD12C8DC7960068730A /* PBXContainerItemProxy */; + }; + D0F7595E2C8DB24400126CF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = D0F7595D2C8DB24400126CF3 /* GRPCSwiftPlugin */; + }; + D0F759602C8DB24400126CF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = D0F7595F2C8DB24400126CF3 /* SwiftProtobufPlugin */; + }; + D0F7598A2C8DB34200126CF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = D0F759892C8DB34200126CF3 /* GRPC */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -286,14 +742,56 @@ }; D05B9F8229E39EED008CB1F9 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D020F64929E4A34B002790F6 /* Burrow.xcconfig */; + baseConfigurationReference = D020F64929E4A34B002790F6 /* App.xcconfig */; buildSettings = { }; name = Debug; }; D05B9F8329E39EED008CB1F9 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D020F64929E4A34B002790F6 /* Burrow.xcconfig */; + baseConfigurationReference = D020F64929E4A34B002790F6 /* App.xcconfig */; + buildSettings = { + }; + name = Release; + }; + D0D4E53D2C8D996F007F820A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0D4E4F72C8D941D007F820A /* Framework.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + D0D4E53E2C8D996F007F820A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0D4E4F72C8D941D007F820A /* Framework.xcconfig */; + buildSettings = { + }; + name = Release; + }; + D0D4E5562C8D9BF2007F820A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0BF09582C8E6789000D8DEC /* UI.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + D0D4E5572C8D9BF2007F820A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0BF09582C8E6789000D8DEC /* UI.xcconfig */; + buildSettings = { + }; + name = Release; + }; + D0D4E5602C8D9BF4007F820A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D00117422B30348D00D87C25 /* Configuration.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + D0D4E5612C8D9BF4007F820A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D00117422B30348D00D87C25 /* Configuration.xcconfig */; buildSettings = { }; name = Release; @@ -319,7 +817,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - D05B9F8129E39EED008CB1F9 /* Build configuration list for PBXNativeTarget "Burrow" */ = { + D05B9F8129E39EED008CB1F9 /* Build configuration list for PBXNativeTarget "App" */ = { isa = XCConfigurationList; buildConfigurations = ( D05B9F8229E39EED008CB1F9 /* Debug */, @@ -328,7 +826,130 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D0D4E53C2C8D996F007F820A /* Build configuration list for PBXNativeTarget "Core" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0D4E53D2C8D996F007F820A /* Debug */, + D0D4E53E2C8D996F007F820A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D0D4E5552C8D9BF2007F820A /* Build configuration list for PBXNativeTarget "UI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0D4E5562C8D9BF2007F820A /* Debug */, + D0D4E5572C8D9BF2007F820A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D0D4E55F2C8D9BF4007F820A /* Build configuration list for PBXNativeTarget "Configuration" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0D4E5602C8D9BF4007F820A /* Debug */, + D0D4E5612C8D9BF4007F820A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + D044EE8F2C8DAB2000778185 /* XCRemoteSwiftPackageReference "swift-nio" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-nio.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.72.0; + }; + }; + D044EE942C8DAB2800778185 /* XCRemoteSwiftPackageReference "swift-nio-transport-services" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-nio-transport-services.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.21.0; + }; + }; + D0B1D10E2C436152004B7823 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-async-algorithms.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.1; + }; + }; + D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/grpc/grpc-swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.23.0; + }; + }; + D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-protobuf.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.28.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + D044EE902C8DAB2000778185 /* NIO */ = { + isa = XCSwiftPackageProductDependency; + package = D044EE8F2C8DAB2000778185 /* XCRemoteSwiftPackageReference "swift-nio" */; + productName = NIO; + }; + D044EE922C8DAB2000778185 /* NIOConcurrencyHelpers */ = { + isa = XCSwiftPackageProductDependency; + package = D044EE8F2C8DAB2000778185 /* XCRemoteSwiftPackageReference "swift-nio" */; + productName = NIOConcurrencyHelpers; + }; + D044EE952C8DAB2800778185 /* NIOTransportServices */ = { + isa = XCSwiftPackageProductDependency; + package = D044EE942C8DAB2800778185 /* XCRemoteSwiftPackageReference "swift-nio-transport-services" */; + productName = NIOTransportServices; + }; + D078F7E02C8DA375008A8CEC /* GRPC */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */; + productName = GRPC; + }; + D078F7E22C8DA375008A8CEC /* SwiftProtobuf */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobuf; + }; + D0B1D10F2C436152004B7823 /* AsyncAlgorithms */ = { + isa = XCSwiftPackageProductDependency; + package = D0B1D10E2C436152004B7823 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */; + productName = AsyncAlgorithms; + }; + D0F7595D2C8DB24400126CF3 /* GRPCSwiftPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */; + productName = "plugin:GRPCSwiftPlugin"; + }; + D0F7595F2C8DB24400126CF3 /* SwiftProtobufPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4852C8D8F29007F820A /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = "plugin:SwiftProtobufPlugin"; + }; + D0F7597D2C8DB30500126CF3 /* CGRPCZlib */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */; + productName = CGRPCZlib; + }; + D0F759892C8DB34200126CF3 /* GRPC */ = { + isa = XCSwiftPackageProductDependency; + package = D0D4E4822C8D8EF6007F820A /* XCRemoteSwiftPackageReference "grpc-swift" */; + productName = GRPC; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = D05B9F6A29E39EEC008CB1F9 /* Project object */; } diff --git a/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..08de0be --- /dev/null +++ b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + diff --git a/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..739b77c --- /dev/null +++ b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,123 @@ +{ + "originHash" : "fa512b990383b7e309c5854a5279817052294a8191a6d3c55c49cfb38e88c0c3", + "pins" : [ + { + "identity" : "grpc-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/grpc/grpc-swift.git", + "state" : { + "revision" : "6a90b7e77e29f9bda6c2b3a4165a40d6c02cfda1", + "version" : "1.23.0" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "6ae9a051f76b81cc668305ceed5b0e0a7fd93d20", + "version" : "1.0.1" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "9bf03ff58ce34478e66aaee630e491823326fd06", + "version" : "1.1.3" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", + "version" : "1.6.1" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "9746cf80e29edfef2a39924a66731249223f42a3", + "version" : "2.72.0" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "d1ead62745cc3269e482f1c51f27608057174379", + "version" : "1.24.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "b5f7062b60e4add1e8c343ba4eb8da2e324b3a94", + "version" : "1.34.0" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "7b84abbdcef69cc3be6573ac12440220789dcd69", + "version" : "2.27.2" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "38ac8221dd20674682148d6451367f89c2652980", + "version" : "1.21.0" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "edb6ed4919f7756157fe02f2552b7e3850a538e5", + "version" : "1.28.1" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "d2ba781702a1d8285419c15ee62fd734a9437ff5", + "version" : "1.3.2" + } + } + ], + "version" : 3 +} diff --git a/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme new file mode 100644 index 0000000..a524e87 --- /dev/null +++ b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/NetworkExtension.xcscheme b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/NetworkExtension.xcscheme new file mode 100644 index 0000000..316c96e --- /dev/null +++ b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/NetworkExtension.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Apple/Configuration/App.xcconfig b/Apple/Configuration/App.xcconfig index f536e9d..e0946f4 100644 --- a/Apple/Configuration/App.xcconfig +++ b/Apple/Configuration/App.xcconfig @@ -1,10 +1,8 @@ - +LD_EXPORT_SYMBOLS = NO SKIP_INSTALL = NO -LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor - -ENABLE_PREVIEWS = YES diff --git a/Apple/Configuration/Compiler.xcconfig b/Apple/Configuration/Compiler.xcconfig index ebf6136..6b071f1 100644 --- a/Apple/Configuration/Compiler.xcconfig +++ b/Apple/Configuration/Compiler.xcconfig @@ -1,61 +1,44 @@ #include "Identity.xcconfig" +#include "Debug.xcconfig" +#include "Version.xcconfig" SDKROOT = auto ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx -IPHONEOS_DEPLOYMENT_TARGET = 15.0 -MACOSX_DEPLOYMENT_TARGET = 12.0 +SWIFT_VERSION = 6.0 +IPHONEOS_DEPLOYMENT_TARGET = 17.0 +MACOSX_DEPLOYMENT_TARGET = 14.0 SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO SUPPORTS_MACCATALYST = NO -ALWAYS_SEARCH_USER_PATHS = NO PRODUCT_NAME = $(TARGET_NAME:c99extidentifier) PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).$(PRODUCT_NAME) -CURRENT_PROJECT_VERSION = 1 MARKETING_VERSION = 0.1 SKIP_INSTALL = YES CODE_SIGN_IDENTITY = Apple Development -INFOPLIST_FILE = Configuration/Info.plist GENERATE_INFOPLIST_FILE = YES -INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023 Hack Club +INFOPLIST_FILE = Configuration/Info.plist +INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023-2024 Hack Club INFOPLIST_KEY_CFBundleDisplayName = Burrow -ENABLE_BITCODE = NO - ENABLE_APP_SANDBOX[sdk=macosx*] = YES -ENABLE_HARDENED_RUNTIME[sdk=macosx*] = YES -COMBINE_HIDPI_IMAGES = YES -COPY_PHASE_STRIP = NO +ENABLE_BITCODE = NO +ALWAYS_SEARCH_USER_PATHS = NO +COMBINE_HIDPI_IMAGES = YES +EAGER_LINKING = YES FUSE_BUILD_SCRIPT_PHASES = YES +SWIFT_EMIT_LOC_STRINGS = YES +LOCALIZATION_PREFERS_STRING_CATALOGS = YES +ENABLE_DEBUG_DYLIB = NO APP_GROUP_IDENTIFIER = group.$(APP_BUNDLE_IDENTIFIER) APP_GROUP_IDENTIFIER[sdk=macosx*] = $(DEVELOPMENT_TEAM).$(APP_BUNDLE_IDENTIFIER) +NETWORK_EXTENSION_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).network -// Swift -SWIFT_VERSION = 5.0 -SWIFT_EMIT_LOC_STRINGS = YES - -// Release -DEBUG_INFORMATION_FORMAT = dwarf-with-dsym -SWIFT_REFLECTION_METADATA_LEVEL = none -SWIFT_COMPILATION_MODE = wholemodule -SWIFT_OPTIMIZATION_LEVEL = -Osize -LLVM_LTO = YES -DEAD_CODE_STRIPPING = YES -VALIDATE_PRODUCT = YES - -// Debug -ONLY_ACTIVE_ARCH[config=Debug] = YES -DEBUG_INFORMATION_FORMAT[config=Debug] = dwarf -ENABLE_TESTABILITY[config=Debug] = YES -SWIFT_OPTIMIZATION_LEVEL[config=Debug] = -Onone -SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug] = DEBUG -SWIFT_COMPILATION_MODE[config=Debug] = singlefile -LLVM_LTO[config=Debug] = NO -DEAD_CODE_STRIPPING[config=Debug] = NO -VALIDATE_PRODUCT[config=Debug] = NO +// https://github.com/grpc/grpc-swift/issues/683#issuecomment-1130118953 +OTHER_SWIFT_FLAGS = $(inherited) -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CNIOAtomics.modulemap -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CNIODarwin.modulemap -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CGRPCZlib.modulemap diff --git a/Apple/Configuration/Configuration.xcconfig b/Apple/Configuration/Configuration.xcconfig new file mode 100644 index 0000000..622950a --- /dev/null +++ b/Apple/Configuration/Configuration.xcconfig @@ -0,0 +1,4 @@ +#include "Framework.xcconfig" + +SWIFT_INCLUDE_PATHS = $(PROJECT_DIR)/Configuration/Constants +GCC_PREPROCESSOR_DEFINITIONS = APP_BUNDLE_IDENTIFIER=$(APP_BUNDLE_IDENTIFIER) APP_GROUP_IDENTIFIER=$(APP_GROUP_IDENTIFIER) NETWORK_EXTENSION_BUNDLE_IDENTIFIER=$(NETWORK_EXTENSION_BUNDLE_IDENTIFIER) diff --git a/Apple/Configuration/Constants/Constants.h b/Apple/Configuration/Constants/Constants.h new file mode 100644 index 0000000..5278b61 --- /dev/null +++ b/Apple/Configuration/Constants/Constants.h @@ -0,0 +1,12 @@ +#import + +#define MACRO_STRING_(m) #m +#define MACRO_STRING(m) @MACRO_STRING_(m) + +NS_ASSUME_NONNULL_BEGIN + +static NSString * const AppBundleIdentifier = MACRO_STRING(APP_BUNDLE_IDENTIFIER); +static NSString * const AppGroupIdentifier = MACRO_STRING(APP_GROUP_IDENTIFIER); +static NSString * const NetworkExtensionBundleIdentifier = MACRO_STRING(NETWORK_EXTENSION_BUNDLE_IDENTIFIER); + +NS_ASSUME_NONNULL_END diff --git a/Apple/Configuration/Constants/Constants.swift b/Apple/Configuration/Constants/Constants.swift new file mode 100644 index 0000000..3f8ae95 --- /dev/null +++ b/Apple/Configuration/Constants/Constants.swift @@ -0,0 +1,38 @@ +@_implementationOnly import CConstants +import OSLog + +public enum Constants { + enum Error: Swift.Error { + case invalidAppGroupIdentifier + } + + public static let bundleIdentifier = AppBundleIdentifier + public static let appGroupIdentifier = AppGroupIdentifier + public static let networkExtensionBundleIdentifier = NetworkExtensionBundleIdentifier + + public static var socketURL: URL { + get throws { + try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory) + } + } + public static var databaseURL: URL { + get throws { + try groupContainerURL.appending(component: "burrow.db", directoryHint: .notDirectory) + } + } + + private static var groupContainerURL: URL { + get throws { try _groupContainerURL.get() } + } + private static let _groupContainerURL: Result = { + switch FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) { + case .some(let url): .success(url) + case .none: .failure(.invalidAppGroupIdentifier) + } + }() +} + +extension Logger { + @_dynamicReplacement(for: subsystem) + public static var subsystem: String { Constants.bundleIdentifier } +} diff --git a/Apple/Configuration/Constants/module.modulemap b/Apple/Configuration/Constants/module.modulemap new file mode 100644 index 0000000..0e60f32 --- /dev/null +++ b/Apple/Configuration/Constants/module.modulemap @@ -0,0 +1,4 @@ +module CConstants { + header "Constants.h" + export * +} diff --git a/Apple/Configuration/Debug.xcconfig b/Apple/Configuration/Debug.xcconfig new file mode 100644 index 0000000..9529dbd --- /dev/null +++ b/Apple/Configuration/Debug.xcconfig @@ -0,0 +1,26 @@ +// Release +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym +SWIFT_COMPILATION_MODE = wholemodule +SWIFT_OPTIMIZATION_LEVEL = -Osize +LLVM_LTO = YES +DEAD_CODE_STRIPPING = YES +STRIP_INSTALLED_PRODUCT = YES +STRIP_SWIFT_SYMBOLS = YES +COPY_PHASE_STRIP = NO +VALIDATE_PRODUCT = YES +ENABLE_MODULE_VERIFIER = YES + +// Debug +ONLY_ACTIVE_ARCH[config=Debug] = YES +DEBUG_INFORMATION_FORMAT[config=Debug] = dwarf +ENABLE_TESTABILITY[config=Debug] = YES +GCC_PREPROCESSOR_DEFINITIONS[config=Debug] = DEBUG=1 $(inherited) +SWIFT_OPTIMIZATION_LEVEL[config=Debug] = -Onone +SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug] = DEBUG +SWIFT_COMPILATION_MODE[config=Debug] = singlefile +LLVM_LTO[config=Debug] = NO +DEAD_CODE_STRIPPING[config=Debug] = NO +VALIDATE_PRODUCT[config=Debug] = NO +STRIP_INSTALLED_PRODUCT[config=Debug] = NO +STRIP_SWIFT_SYMBOLS[config=Debug] = NO +ENABLE_MODULE_VERIFIER[config=Debug] = NO diff --git a/Apple/Configuration/Extension.xcconfig b/Apple/Configuration/Extension.xcconfig index dfe9f5c..5885c31 100644 --- a/Apple/Configuration/Extension.xcconfig +++ b/Apple/Configuration/Extension.xcconfig @@ -1,2 +1,6 @@ +LD_EXPORT_SYMBOLS = NO + +OTHER_SWIFT_FLAGS = $(inherited) -Xfrontend -disable-autolink-framework -Xfrontend UIKit -Xfrontend -disable-autolink-framework -Xfrontend AppKit -Xfrontend -disable-autolink-framework -Xfrontend SwiftUI + LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @executable_path/../../Frameworks -LD_RUNPATH_SEARCH_PATHS[sdk=macos*] = $(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks +LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks diff --git a/Apple/Configuration/Framework.xcconfig b/Apple/Configuration/Framework.xcconfig new file mode 100644 index 0000000..6fa4f19 --- /dev/null +++ b/Apple/Configuration/Framework.xcconfig @@ -0,0 +1,14 @@ +PRODUCT_NAME = Burrow$(TARGET_NAME:c99extidentifier) +PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).$(TARGET_NAME:c99extidentifier) +APPLICATION_EXTENSION_API_ONLY = YES +SWIFT_INSTALL_OBJC_HEADER = NO +SWIFT_SKIP_AUTOLINKING_FRAMEWORKS = YES +SWIFT_SKIP_AUTOLINKING_LIBRARIES = YES + +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks +LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks + +DYLIB_INSTALL_NAME_BASE = @rpath +DYLIB_COMPATIBILITY_VERSION = 1 +DYLIB_CURRENT_VERSION = 1 +VERSIONING_SYSTEM = diff --git a/Apple/Configuration/Identity.xcconfig b/Apple/Configuration/Identity.xcconfig index e4ef90e..e9ca78d 100644 --- a/Apple/Configuration/Identity.xcconfig +++ b/Apple/Configuration/Identity.xcconfig @@ -1,2 +1,2 @@ -DEVELOPMENT_TEAM = 87PW93R2ZR +DEVELOPMENT_TEAM = P6PV2R9443 APP_BUNDLE_IDENTIFIER = com.hackclub.burrow diff --git a/Apple/Configuration/Version.xcconfig b/Apple/Configuration/Version.xcconfig new file mode 100644 index 0000000..e69de29 diff --git a/Apple/Core/Client.swift b/Apple/Core/Client.swift new file mode 100644 index 0000000..8874e3b --- /dev/null +++ b/Apple/Core/Client.swift @@ -0,0 +1,32 @@ +import GRPC +import NIOTransportServices + +public typealias TunnelClient = Burrow_TunnelAsyncClient +public typealias NetworksClient = Burrow_NetworksAsyncClient + +public protocol Client { + init(channel: GRPCChannel) +} + +extension Client { + public static func unix(socketURL: URL) -> Self { + let group = NIOTSEventLoopGroup() + let configuration = ClientConnection.Configuration.default( + target: .unixDomainSocket(socketURL.path), + eventLoopGroup: group + ) + return Self(channel: ClientConnection(configuration: configuration)) + } +} + +extension TunnelClient: Client { + public init(channel: any GRPCChannel) { + self.init(channel: channel, defaultCallOptions: .init(), interceptors: .none) + } +} + +extension NetworksClient: Client { + public init(channel: any GRPCChannel) { + self.init(channel: channel, defaultCallOptions: .init(), interceptors: .none) + } +} diff --git a/Apple/Core/Client/burrow.proto b/Apple/Core/Client/burrow.proto new file mode 120000 index 0000000..03e86a5 --- /dev/null +++ b/Apple/Core/Client/burrow.proto @@ -0,0 +1 @@ +../../../proto/burrow.proto \ No newline at end of file diff --git a/Apple/Core/Client/grpc-swift-config.json b/Apple/Core/Client/grpc-swift-config.json new file mode 100644 index 0000000..2d89698 --- /dev/null +++ b/Apple/Core/Client/grpc-swift-config.json @@ -0,0 +1,11 @@ +{ + "invocations": [ + { + "protoFiles": [ + "burrow.proto", + ], + "server": false, + "visibility": "public" + } + ] +} diff --git a/Apple/Core/Client/swift-protobuf-config.json b/Apple/Core/Client/swift-protobuf-config.json new file mode 100644 index 0000000..87aaec3 --- /dev/null +++ b/Apple/Core/Client/swift-protobuf-config.json @@ -0,0 +1,10 @@ +{ + "invocations": [ + { + "protoFiles": [ + "burrow.proto", + ], + "visibility": "public" + } + ] +} diff --git a/Apple/Core/Logging.swift b/Apple/Core/Logging.swift new file mode 100644 index 0000000..ba40888 --- /dev/null +++ b/Apple/Core/Logging.swift @@ -0,0 +1,19 @@ +import os +@_exported import OSLog + +extension Logger { + private static let loggers: OSAllocatedUnfairLock<[String: Logger]> = OSAllocatedUnfairLock(initialState: [:]) + + public dynamic static var subsystem: String { "com.hackclub.burrow" } + + public static func logger(for type: Any.Type) -> Logger { + let category = String(describing: type) + let logger = loggers.withLock { loggers in + if let logger = loggers[category] { return logger } + let logger = Logger(subsystem: subsystem, category: category) + loggers[category] = logger + return logger + } + return logger + } +} diff --git a/Apple/NetworkExtension/Info.plist b/Apple/NetworkExtension/Info.plist index 3059459..e2cf604 100644 --- a/Apple/NetworkExtension/Info.plist +++ b/Apple/NetworkExtension/Info.plist @@ -2,6 +2,8 @@ + CFBundleName + $(INFOPLIST_KEY_CFBundleDisplayName) NSExtension NSExtensionPointIdentifier diff --git a/Apple/NetworkExtension/NetworkExtension-iOS.entitlements b/Apple/NetworkExtension/NetworkExtension-iOS.entitlements index d9849a8..02ee960 100644 --- a/Apple/NetworkExtension/NetworkExtension-iOS.entitlements +++ b/Apple/NetworkExtension/NetworkExtension-iOS.entitlements @@ -2,6 +2,10 @@ + com.apple.developer.networking.networkextension + + packet-tunnel-provider + com.apple.security.application-groups $(APP_GROUP_IDENTIFIER) diff --git a/Apple/NetworkExtension/NetworkExtension-macOS.entitlements b/Apple/NetworkExtension/NetworkExtension-macOS.entitlements index d9849a8..63efcfc 100644 --- a/Apple/NetworkExtension/NetworkExtension-macOS.entitlements +++ b/Apple/NetworkExtension/NetworkExtension-macOS.entitlements @@ -2,9 +2,19 @@ + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.security.app-sandbox + com.apple.security.application-groups $(APP_GROUP_IDENTIFIER) + com.apple.security.network.client + + com.apple.security.network.server + diff --git a/Apple/NetworkExtension/NetworkExtension.xcconfig b/Apple/NetworkExtension/NetworkExtension.xcconfig index f606079..35c7198 100644 --- a/Apple/NetworkExtension/NetworkExtension.xcconfig +++ b/Apple/NetworkExtension/NetworkExtension.xcconfig @@ -1,8 +1,11 @@ #include "../Configuration/Extension.xcconfig" PRODUCT_NAME = BurrowNetworkExtension -PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).network +PRODUCT_BUNDLE_IDENTIFIER = $(NETWORK_EXTENSION_BUNDLE_IDENTIFIER) + INFOPLIST_FILE = NetworkExtension/Info.plist CODE_SIGN_ENTITLEMENTS = NetworkExtension/NetworkExtension-iOS.entitlements -CODE_SIGN_ENTITLEMENTS[sdk=macos*] = NetworkExtension/NetworkExtension-macOS.entitlements +CODE_SIGN_ENTITLEMENTS[sdk=macosx*] = NetworkExtension/NetworkExtension-macOS.entitlements + +SWIFT_INCLUDE_PATHS = $(inherited) $(PROJECT_DIR)/NetworkExtension diff --git a/Apple/NetworkExtension/PacketTunnelProvider.swift b/Apple/NetworkExtension/PacketTunnelProvider.swift index c8a87cf..a8e42e0 100644 --- a/Apple/NetworkExtension/PacketTunnelProvider.swift +++ b/Apple/NetworkExtension/PacketTunnelProvider.swift @@ -1,29 +1,74 @@ +import AsyncAlgorithms +import BurrowConfiguration +import BurrowCore +import libburrow import NetworkExtension +import os class PacketTunnelProvider: NEPacketTunnelProvider { + enum Error: Swift.Error { + case missingTunnelConfiguration + } - override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { - // Add code here to start the process of connecting the tunnel. + private let logger = Logger.logger(for: PacketTunnelProvider.self) + + private var client: TunnelClient { + get throws { try _client.get() } } - - override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - // Add code here to start the process of stopping the tunnel. - completionHandler() + private let _client: Result = Result { + try TunnelClient.unix(socketURL: Constants.socketURL) } - - override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { - // Add code here to handle the message. - if let handler = completionHandler { - handler(messageData) + + override init() { + do { + libburrow.spawnInProcess( + socketPath: try Constants.socketURL.path(percentEncoded: false), + databasePath: try Constants.databaseURL.path(percentEncoded: false) + ) + } catch { + logger.error("Failed to spawn networking thread: \(error)") } } - - override func sleep(completionHandler: @escaping () -> Void) { - // Add code here to get ready to sleep. - completionHandler() + + override func startTunnel(options: [String: NSObject]? = nil) async throws { + do { + let configuration = try await Array(client.tunnelConfiguration(.init()).prefix(1)).first + guard let settings = configuration?.settings else { + throw Error.missingTunnelConfiguration + } + try await setTunnelNetworkSettings(settings) + _ = try await client.tunnelStart(.init()) + logger.log("Started tunnel with network settings: \(settings)") + } catch { + logger.error("Failed to start tunnel: \(error)") + throw error + } } - - override func wake() { - // Add code here to wake up. + + override func stopTunnel(with reason: NEProviderStopReason) async { + do { + _ = try await client.tunnelStop(.init()) + logger.log("Stopped client") + } catch { + logger.error("Failed to stop tunnel: \(error)") + } + } +} + +extension Burrow_TunnelConfigurationResponse { + fileprivate var settings: NEPacketTunnelNetworkSettings { + let ipv6Addresses = addresses.filter { IPv6Address($0) != nil } + + let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1") + settings.mtu = NSNumber(value: mtu) + settings.ipv4Settings = NEIPv4Settings( + addresses: addresses.filter { IPv4Address($0) != nil }, + subnetMasks: ["255.255.255.0"] + ) + settings.ipv6Settings = NEIPv6Settings( + addresses: ipv6Addresses, + networkPrefixLengths: ipv6Addresses.map { _ in 64 } + ) + return settings } } diff --git a/Apple/NetworkExtension/libburrow/build-rust.sh b/Apple/NetworkExtension/libburrow/build-rust.sh new file mode 100755 index 0000000..6f455a9 --- /dev/null +++ b/Apple/NetworkExtension/libburrow/build-rust.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# This is a build script. It is run by Xcode as a build step. +# The type of build is described in various environment variables set by Xcode. + +export PATH="${PATH}:${HOME}/.cargo/bin:/opt/homebrew/bin:/usr/local/bin:/etc/profiles/per-user/${USER}/bin" + +if ! [[ -x "$(command -v cargo)" ]]; then + echo 'error: Unable to find cargo' + exit 127 +fi + +set -e + +# Change directories relative to the location of this script +cd -- "$(dirname -- "${BASH_SOURCE[0]}")"/../../../burrow + +RUST_TARGETS=() + +# Match the PLATFORM_NAME (iphoneos) and ARCHS (arm64) to a set of RUST_TARGETS (aarch64-apple-ios) +IFS=' ' read -a BURROW_ARCHS <<< "${ARCHS[@]}" +for ARCH in "${BURROW_ARCHS[@]}"; do + case $PLATFORM_NAME in + iphonesimulator) + case $ARCH in + arm64) RUST_TARGETS+=("aarch64-apple-ios-sim") ;; + x86_64) RUST_TARGETS+=("x86_64-apple-ios") ;; + *) echo "error: Unknown $PLATFORM_NAME arch, $ARCH"; exit 1 ;; + esac + ;; + iphoneos) + case $ARCH in + arm64) RUST_TARGETS+=("aarch64-apple-ios") ;; + *) echo "error: Unknown $PLATFORM_NAME arch, $ARCH"; exit 1 ;; + esac + ;; + macos*) + case $ARCH in + arm64) RUST_TARGETS+=("aarch64-apple-darwin") ;; + x86_64) RUST_TARGETS+=("x86_64-apple-darwin") ;; + *) echo "error: Unknown $PLATFORM_NAME arch, $ARCH"; exit 1 ;; + esac + ;; + *) echo "error: Unsupported platform $PLATFORM_NAME"; exit 1 ;; + esac +done + +# Pass all RUST_TARGETS in a single invocation +CARGO_ARGS=() +for TARGET in "${RUST_TARGETS[@]}"; do + CARGO_ARGS+=("--target") + CARGO_ARGS+=("$TARGET") +done + +CARGO_ARGS+=("--lib") + +# Pass the configuration (Debug or Release) through to cargo +if [[ $SWIFT_ACTIVE_COMPILATION_CONDITIONS == *DEBUG* ]]; then + CARGO_TARGET_SUBDIR="debug" +else + CARGO_ARGS+=("--release") + CARGO_TARGET_SUBDIR="release" +fi + +if [[ -x "$(command -v rustup)" ]]; then + CARGO_PATH="$(dirname $(rustup which cargo)):/usr/bin" +else + CARGO_PATH="$(dirname $(readlink -f $(which cargo))):/usr/bin" +fi + +PROTOC=$(readlink -f $(which protoc)) +CARGO_PATH="$(dirname $PROTOC):$CARGO_PATH" + +# Run cargo without the various environment variables set by Xcode. +# Those variables can confuse cargo and the build scripts it runs. +env -i PATH="$CARGO_PATH" PROTOC="$PROTOC" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" IPHONEOS_DEPLOYMENT_TARGET="$IPHONEOS_DEPLOYMENT_TARGET" MACOSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" cargo build "${CARGO_ARGS[@]}" + +mkdir -p "${BUILT_PRODUCTS_DIR}" + +# Use `lipo` to merge the architectures together into BUILT_PRODUCTS_DIR +/usr/bin/xcrun --sdk $PLATFORM_NAME lipo \ + -create $(printf "${CONFIGURATION_TEMP_DIR}/target/%q/${CARGO_TARGET_SUBDIR}/libburrow.a " "${RUST_TARGETS[@]}") \ + -output "${BUILT_PRODUCTS_DIR}/libburrow.a" diff --git a/Apple/NetworkExtension/libburrow/libburrow.h b/Apple/NetworkExtension/libburrow/libburrow.h new file mode 100644 index 0000000..59b4734 --- /dev/null +++ b/Apple/NetworkExtension/libburrow/libburrow.h @@ -0,0 +1,2 @@ +__attribute__((__swift_name__("spawnInProcess(socketPath:databasePath:)"))) +extern void spawn_in_process(const char * __nullable socket_path, const char * __nullable db_path); diff --git a/Apple/NetworkExtension/libburrow/module.modulemap b/Apple/NetworkExtension/libburrow/module.modulemap new file mode 100644 index 0000000..c53a032 --- /dev/null +++ b/Apple/NetworkExtension/libburrow/module.modulemap @@ -0,0 +1,4 @@ +module libburrow { + header "libburrow.h" + export * +} diff --git a/Apple/Profiles/Burrow_Developer_ID.provisionprofile b/Apple/Profiles/Burrow_Developer_ID.provisionprofile new file mode 100644 index 0000000..3ecd831 Binary files /dev/null and b/Apple/Profiles/Burrow_Developer_ID.provisionprofile differ diff --git a/Apple/Profiles/Burrow_Network_Developer_ID.provisionprofile b/Apple/Profiles/Burrow_Network_Developer_ID.provisionprofile new file mode 100644 index 0000000..3ce7e37 Binary files /dev/null and b/Apple/Profiles/Burrow_Network_Developer_ID.provisionprofile differ diff --git a/Apple/App/Assets.xcassets/AccentColor.colorset/Contents.json b/Apple/UI/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from Apple/App/Assets.xcassets/AccentColor.colorset/Contents.json rename to Apple/UI/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/100.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..f86c139 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/1024.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..872c9ce Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/114.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..3bb278d Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/120.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..185615e Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/128.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100644 index 0000000..51bd97c Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/144.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..b05e371 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/152.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..c95ea8a Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/16.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 0000000..3cb15a5 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/167.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..a3ad6a2 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/172.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/172.png new file mode 100644 index 0000000..9f3bdb4 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/172.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/180.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..53c1237 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/196.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/196.png new file mode 100644 index 0000000..ea95961 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/196.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/20.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..aec8236 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/216.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/216.png new file mode 100644 index 0000000..9f0e3ce Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/216.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/256.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 0000000..a82ce93 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/29.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..8dc25c1 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/32.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 0000000..655a424 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/40.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..1f7f5e9 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/48.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/48.png new file mode 100644 index 0000000..4a67ebf Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/48.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/50.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..88985d8 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/512.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 0000000..e5cbf6a Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/55.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/55.png new file mode 100644 index 0000000..dc079ea Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/55.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/57.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..de4fddc Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/58.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..961adad Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/60.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..2a9e939 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/64.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 0000000..c67e407 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/72.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..d09aebe Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/76.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..3e649b6 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/80.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..6dad29f Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/87.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..a8ccb38 Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/88.png b/Apple/UI/Assets.xcassets/AppIcon.appiconset/88.png new file mode 100644 index 0000000..b1a478a Binary files /dev/null and b/Apple/UI/Assets.xcassets/AppIcon.appiconset/88.png differ diff --git a/Apple/UI/Assets.xcassets/AppIcon.appiconset/Contents.json b/Apple/UI/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..f78687a --- /dev/null +++ b/Apple/UI/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,344 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + }, + { + "filename" : "48.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "24x24", + "subtype" : "38mm" + }, + { + "filename" : "55.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "27.5x27.5", + "subtype" : "42mm" + }, + { + "filename" : "58.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "33x33", + "subtype" : "45mm" + }, + { + "filename" : "80.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "40x40", + "subtype" : "38mm" + }, + { + "filename" : "88.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "44x44", + "subtype" : "40mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "46x46", + "subtype" : "41mm" + }, + { + "filename" : "100.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "50x50", + "subtype" : "44mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "51x51", + "subtype" : "45mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "54x54", + "subtype" : "49mm" + }, + { + "filename" : "172.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "86x86", + "subtype" : "38mm" + }, + { + "filename" : "196.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "98x98", + "subtype" : "42mm" + }, + { + "filename" : "216.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "108x108", + "subtype" : "44mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "117x117", + "subtype" : "45mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "129x129", + "subtype" : "49mm" + }, + { + "filename" : "1024.png", + "idiom" : "watch-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Apple/App/Assets.xcassets/Contents.json b/Apple/UI/Assets.xcassets/Contents.json similarity index 100% rename from Apple/App/Assets.xcassets/Contents.json rename to Apple/UI/Assets.xcassets/Contents.json diff --git a/Apple/UI/Assets.xcassets/HackClub.colorset/Contents.json b/Apple/UI/Assets.xcassets/HackClub.colorset/Contents.json new file mode 100644 index 0000000..911b4b1 --- /dev/null +++ b/Apple/UI/Assets.xcassets/HackClub.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x50", + "green" : "0x37", + "red" : "0xEC" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Apple/UI/Assets.xcassets/HackClub.imageset/Contents.json b/Apple/UI/Assets.xcassets/HackClub.imageset/Contents.json new file mode 100644 index 0000000..ddd0664 --- /dev/null +++ b/Apple/UI/Assets.xcassets/HackClub.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "flag-standalone-wtransparent.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Apple/UI/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf b/Apple/UI/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf new file mode 100644 index 0000000..1506fe9 Binary files /dev/null and b/Apple/UI/Assets.xcassets/HackClub.imageset/flag-standalone-wtransparent.pdf differ diff --git a/Apple/UI/Assets.xcassets/WireGuard.colorset/Contents.json b/Apple/UI/Assets.xcassets/WireGuard.colorset/Contents.json new file mode 100644 index 0000000..092ec69 --- /dev/null +++ b/Apple/UI/Assets.xcassets/WireGuard.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x1A", + "green" : "0x17", + "red" : "0x88" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Apple/UI/Assets.xcassets/WireGuard.imageset/Contents.json b/Apple/UI/Assets.xcassets/WireGuard.imageset/Contents.json new file mode 100644 index 0000000..e7fe15a --- /dev/null +++ b/Apple/UI/Assets.xcassets/WireGuard.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "WireGuard.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Apple/UI/Assets.xcassets/WireGuard.imageset/WireGuard.svg b/Apple/UI/Assets.xcassets/WireGuard.imageset/WireGuard.svg new file mode 100644 index 0000000..9520f89 --- /dev/null +++ b/Apple/UI/Assets.xcassets/WireGuard.imageset/WireGuard.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Apple/UI/Assets.xcassets/WireGuardTitle.imageset/Contents.json b/Apple/UI/Assets.xcassets/WireGuardTitle.imageset/Contents.json new file mode 100644 index 0000000..782dd12 --- /dev/null +++ b/Apple/UI/Assets.xcassets/WireGuardTitle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "WireGuardTitle.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Apple/UI/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg b/Apple/UI/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg new file mode 100644 index 0000000..64946da --- /dev/null +++ b/Apple/UI/Assets.xcassets/WireGuardTitle.imageset/WireGuardTitle.svg @@ -0,0 +1,3 @@ + + + diff --git a/Apple/UI/BurrowView.swift b/Apple/UI/BurrowView.swift new file mode 100644 index 0000000..96467c7 --- /dev/null +++ b/Apple/UI/BurrowView.swift @@ -0,0 +1,75 @@ +import AuthenticationServices +import SwiftUI + +#if !os(macOS) +public struct BurrowView: View { + @Environment(\.webAuthenticationSession) + private var webAuthenticationSession + + public var body: some View { + NavigationStack { + VStack { + HStack { + Text("Networks") + .font(.largeTitle) + .fontWeight(.bold) + Spacer() + Menu { + Button("Hack Club", action: addHackClubNetwork) + Button("WireGuard", action: addWireGuardNetwork) + } label: { + Image(systemName: "plus.circle.fill") + .font(.title) + .accessibilityLabel("Add") + } + } + .padding(.top) + NetworkCarouselView() + Spacer() + TunnelStatusView() + TunnelButton() + .padding(.bottom) + } + .padding() + .handleOAuth2Callback() + } + } + + public init() { + } + + private func addHackClubNetwork() { + Task { + try await authenticateWithSlack() + } + } + + private func addWireGuardNetwork() { + } + + private func authenticateWithSlack() async throws { + guard + let authorizationEndpoint = URL(string: "https://slack.com/openid/connect/authorize"), + let tokenEndpoint = URL(string: "https://slack.com/api/openid.connect.token"), + let redirectURI = URL(string: "https://burrow.rs/callback/oauth2") else { return } + let session = OAuth2.Session( + authorizationEndpoint: authorizationEndpoint, + tokenEndpoint: tokenEndpoint, + redirectURI: redirectURI, + scopes: ["openid", "profile"], + clientID: "2210535565.6884042183125", + clientSecret: "2793c8a5255cae38830934c664eeb62d" + ) + let response = try await session.authorize(webAuthenticationSession) + } +} + +#if DEBUG +struct NetworkView_Previews: PreviewProvider { + static var previews: some View { + BurrowView() + .environment(\.tunnel, PreviewTunnel()) + } +} +#endif +#endif diff --git a/Apple/UI/FloatingButtonStyle.swift b/Apple/UI/FloatingButtonStyle.swift new file mode 100644 index 0000000..53ab5ed --- /dev/null +++ b/Apple/UI/FloatingButtonStyle.swift @@ -0,0 +1,50 @@ +import SwiftUI + +struct FloatingButtonStyle: ButtonStyle { + static let duration = 0.08 + + var color: Color + var cornerRadius: CGFloat + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(.headline) + .foregroundColor(.white) + .frame(minHeight: 48) + .padding(.horizontal) + .background( + RoundedRectangle(cornerRadius: cornerRadius) + .fill( + LinearGradient( + colors: [ + configuration.isPressed ? color.opacity(0.9) : color.opacity(0.9), + configuration.isPressed ? color.opacity(0.9) : color + ], + startPoint: .init(x: 0.2, y: 0), + endPoint: .init(x: 0.8, y: 1) + ) + ) + .background( + RoundedRectangle(cornerRadius: cornerRadius) + .fill(configuration.isPressed ? .black : .white) + ) + ) + .shadow(color: .black.opacity(configuration.isPressed ? 0.0 : 0.1), radius: 2.5, x: 0, y: 2) + .scaleEffect(configuration.isPressed ? 0.975 : 1.0) + .padding(.bottom, 2) + .animation( + configuration.isPressed ? .easeOut(duration: Self.duration) : .easeIn(duration: Self.duration), + value: configuration.isPressed + ) + } +} + +extension ButtonStyle where Self == FloatingButtonStyle { + static var floating: FloatingButtonStyle { + floating() + } + + static func floating(color: Color = .accentColor, cornerRadius: CGFloat = 10) -> FloatingButtonStyle { + FloatingButtonStyle(color: color, cornerRadius: cornerRadius) + } +} diff --git a/Apple/UI/MenuItemToggleView.swift b/Apple/UI/MenuItemToggleView.swift new file mode 100644 index 0000000..ef5e8ee --- /dev/null +++ b/Apple/UI/MenuItemToggleView.swift @@ -0,0 +1,67 @@ +// +// MenuItemToggleView.swift +// App +// +// Created by Thomas Stubblefield on 5/13/23. +// + +import SwiftUI + +public struct MenuItemToggleView: View { + @Environment(\.tunnel) + var tunnel: Tunnel + + public var body: some View { + HStack { + VStack(alignment: .leading) { + Text("Burrow") + .font(.headline) + Text(tunnel.status.description) + .font(.subheadline) + } + Spacer() + Toggle(isOn: tunnel.toggleIsOn) { + } + .disabled(tunnel.toggleDisabled) + .toggleStyle(.switch) + } + .accessibilityElement(children: .combine) + .padding(.horizontal, 4) + .padding(10) + .frame(minWidth: 300, minHeight: 32, maxHeight: 32) + } + + public init() { + } +} + +extension Tunnel { + @MainActor fileprivate var toggleDisabled: Bool { + switch status { + case .disconnected, .permissionRequired, .connected, .disconnecting: + false + case .unknown, .disabled, .connecting, .reasserting, .invalid, .configurationReadWriteFailed: + true + } + } + + @MainActor var toggleIsOn: Binding { + Binding { + switch status { + case .connecting, .reasserting, .connected: + true + default: + false + } + } set: { newValue in + switch (status, newValue) { + case (.permissionRequired, true): + enable() + case (_, true): + start() + case (_, false): + stop() + } + } + } +} diff --git a/Apple/UI/NetworkCarouselView.swift b/Apple/UI/NetworkCarouselView.swift new file mode 100644 index 0000000..f969356 --- /dev/null +++ b/Apple/UI/NetworkCarouselView.swift @@ -0,0 +1,39 @@ +import SwiftUI + +struct NetworkCarouselView: View { + var networks: [any Network] = [ + HackClub(id: 1), + HackClub(id: 2), + WireGuard(id: 4), + HackClub(id: 5) + ] + + var body: some View { + ScrollView(.horizontal) { + LazyHStack { + ForEach(networks, id: \.id) { network in + NetworkView(network: network) + .containerRelativeFrame(.horizontal, count: 10, span: 7, spacing: 0, alignment: .center) + .scrollTransition(.interactive, axis: .horizontal) { content, phase in + content + .scaleEffect(1.0 - abs(phase.value) * 0.1) + } + } + } + } + .scrollTargetLayout() + .scrollClipDisabled() + .scrollIndicators(.hidden) + .defaultScrollAnchor(.center) + .scrollTargetBehavior(.viewAligned) + .containerRelativeFrame(.horizontal) + } +} + +#if DEBUG +struct NetworkCarouselView_Previews: PreviewProvider { + static var previews: some View { + NetworkCarouselView() + } +} +#endif diff --git a/Apple/UI/NetworkExtension+Async.swift b/Apple/UI/NetworkExtension+Async.swift new file mode 100644 index 0000000..5820e7f --- /dev/null +++ b/Apple/UI/NetworkExtension+Async.swift @@ -0,0 +1,45 @@ +import NetworkExtension + +extension NEVPNManager: @unchecked @retroactive Sendable { + func remove() async throws { + _ = try await withUnsafeThrowingContinuation { continuation in + removeFromPreferences(completionHandler: completion(continuation)) + } + } + + func save() async throws { + _ = try await withUnsafeThrowingContinuation { continuation in + saveToPreferences(completionHandler: completion(continuation)) + } + } +} + +extension NETunnelProviderManager: @unchecked @retroactive Sendable { + class var managers: [NETunnelProviderManager] { + get async throws { + try await withUnsafeThrowingContinuation { continuation in + loadAllFromPreferences(completionHandler: completion(continuation)) + } + } + } +} + +private func completion(_ continuation: UnsafeContinuation) -> (Error?) -> Void { + return { error in + if let error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: ()) + } + } +} + +private func completion(_ continuation: UnsafeContinuation) -> (T?, Error?) -> Void { + return { value, error in + if let error { + continuation.resume(throwing: error) + } else if let value { + continuation.resume(returning: value) + } + } +} diff --git a/Apple/UI/NetworkExtensionTunnel.swift b/Apple/UI/NetworkExtensionTunnel.swift new file mode 100644 index 0000000..7aaa3b1 --- /dev/null +++ b/Apple/UI/NetworkExtensionTunnel.swift @@ -0,0 +1,171 @@ +import BurrowCore +import NetworkExtension + +@Observable +public final class NetworkExtensionTunnel: Tunnel { + @MainActor public private(set) var status: TunnelStatus = .unknown + @MainActor private var error: NEVPNError? + + private let logger = Logger.logger(for: Tunnel.self) + private let bundleIdentifier: String + private let configurationChanged: Task + private let statusChanged: Task + + // Each manager corresponds to one entry in the Settings app. + // Our goal is to maintain a single manager, so we create one if none exist and delete any extra. + @MainActor private var managers: [NEVPNManager]? { + didSet { Task { await updateStatus() } } + } + + @MainActor private var currentStatus: TunnelStatus { + guard let managers = managers else { + guard let error = error else { + return .unknown + } + + switch error.code { + case .configurationReadWriteFailed: + return .configurationReadWriteFailed + default: + return .unknown + } + } + + guard let manager = managers.first else { + return .permissionRequired + } + + guard manager.isEnabled else { + return .disabled + } + + return manager.connection.tunnelStatus + } + + public init(bundleIdentifier: String) { + self.bundleIdentifier = bundleIdentifier + + let center = NotificationCenter.default + let tunnel: OSAllocatedUnfairLock = .init(initialState: .none) + configurationChanged = Task { + for try await _ in center.notifications(named: .NEVPNConfigurationChange) { + try Task.checkCancellation() + await tunnel.withLock { $0 }?.update() + } + } + statusChanged = Task { + for try await _ in center.notifications(named: .NEVPNStatusDidChange) { + try Task.checkCancellation() + await tunnel.withLock { $0 }?.updateStatus() + } + } + tunnel.withLock { $0 = self } + + Task { await update() } + } + + private func update() async { + do { + let result = try await NETunnelProviderManager.managers + await MainActor.run { + managers = result + status = currentStatus + } + await self.updateStatus() + } catch let vpnError as NEVPNError { + await MainActor.run { + error = vpnError + } + } catch { + logger.error("Failed to update VPN configurations: \(error)") + } + } + + private func updateStatus() async { + await MainActor.run { + status = currentStatus + } + } + + func configure() async throws { + let managers = try await NETunnelProviderManager.managers + if managers.count > 1 { + try await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in + for manager in managers.suffix(from: 1) { + group.addTask { try await manager.remove() } + } + try await group.waitForAll() + } + } + + guard managers.isEmpty else { return } + + let manager = NETunnelProviderManager() + manager.localizedDescription = "Burrow" + + let proto = NETunnelProviderProtocol() + proto.providerBundleIdentifier = bundleIdentifier + proto.serverAddress = "hackclub.com" + + manager.protocolConfiguration = proto + try await manager.save() + } + + public func start() { + Task { + guard let manager = try await NETunnelProviderManager.managers.first else { return } + do { + if !manager.isEnabled { + manager.isEnabled = true + try await manager.save() + } + try manager.connection.startVPNTunnel() + } catch { + logger.error("Failed to start: \(error)") + } + } + } + + public func stop() { + Task { + guard let manager = try await NETunnelProviderManager.managers.first else { return } + manager.connection.stopVPNTunnel() + } + } + + public func enable() { + Task { + do { + try await configure() + } catch { + logger.error("Failed to enable: \(error)") + } + } + } + + deinit { + configurationChanged.cancel() + statusChanged.cancel() + } +} + +extension NEVPNConnection { + fileprivate var tunnelStatus: TunnelStatus { + switch status { + case .connected: + .connected(connectedDate!) + case .connecting: + .connecting + case .disconnecting: + .disconnecting + case .disconnected: + .disconnected + case .reasserting: + .reasserting + case .invalid: + .invalid + @unknown default: + .unknown + } + } +} diff --git a/Apple/UI/NetworkView.swift b/Apple/UI/NetworkView.swift new file mode 100644 index 0000000..b839d65 --- /dev/null +++ b/Apple/UI/NetworkView.swift @@ -0,0 +1,38 @@ +import SwiftUI + +struct NetworkView: View { + var color: Color + var content: () -> Content + + private var gradient: LinearGradient { + LinearGradient( + colors: [ + color.opacity(0.8), + color + ], + startPoint: .init(x: 0.2, y: 0), + endPoint: .init(x: 0.8, y: 1) + ) + } + + var body: some View { + content() + .frame(maxWidth: .infinity, minHeight: 175, maxHeight: 175) + .background( + RoundedRectangle(cornerRadius: 10) + .fill(gradient) + .background( + RoundedRectangle(cornerRadius: 10) + .fill(.white) + ) + ) + .shadow(color: .black.opacity(0.1), radius: 3.0, x: 0, y: 2) + } +} + +extension NetworkView where Content == AnyView { + init(network: any Network) { + color = network.backgroundColor + content = { AnyView(network.label) } + } +} diff --git a/Apple/UI/Networks/HackClub.swift b/Apple/UI/Networks/HackClub.swift new file mode 100644 index 0000000..b1c2023 --- /dev/null +++ b/Apple/UI/Networks/HackClub.swift @@ -0,0 +1,27 @@ +import BurrowCore +import SwiftUI + +struct HackClub: Network { + typealias NetworkType = Burrow_WireGuardNetwork + static let type: Burrow_NetworkType = .hackClub + + var id: Int32 + var backgroundColor: Color { .init("HackClub") } + + @MainActor var label: some View { + GeometryReader { reader in + VStack(alignment: .leading) { + Image("HackClub") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: reader.size.height / 4) + Spacer() + Text("@conradev") + .foregroundStyle(.white) + .font(.body.monospaced()) + } + .padding() + .frame(maxWidth: .infinity) + } + } +} diff --git a/Apple/UI/Networks/Network.swift b/Apple/UI/Networks/Network.swift new file mode 100644 index 0000000..c6d5fba --- /dev/null +++ b/Apple/UI/Networks/Network.swift @@ -0,0 +1,36 @@ +import Atomics +import BurrowCore +import SwiftProtobuf +import SwiftUI + +protocol Network { + associatedtype NetworkType: Message + associatedtype Label: View + + static var type: Burrow_NetworkType { get } + + var id: Int32 { get } + var backgroundColor: Color { get } + + @MainActor var label: Label { get } +} + +@Observable +@MainActor +final class NetworkViewModel: Sendable { + private(set) var networks: [Burrow_Network] = [] + + private var task: Task! + + init(socketURL: URL) { + task = Task { [weak self] in + let client = NetworksClient.unix(socketURL: socketURL) + for try await networks in client.networkList(.init()) { + guard let viewModel = self else { continue } + Task { @MainActor in + viewModel.networks = networks.network + } + } + } + } +} diff --git a/Apple/UI/Networks/WireGuard.swift b/Apple/UI/Networks/WireGuard.swift new file mode 100644 index 0000000..cba67ef --- /dev/null +++ b/Apple/UI/Networks/WireGuard.swift @@ -0,0 +1,34 @@ +import BurrowCore +import SwiftUI + +struct WireGuard: Network { + typealias NetworkType = Burrow_WireGuardNetwork + static let type: BurrowCore.Burrow_NetworkType = .wireGuard + + var id: Int32 + var backgroundColor: Color { .init("WireGuard") } + + @MainActor var label: some View { + GeometryReader { reader in + VStack(alignment: .leading) { + HStack { + Image("WireGuard") + .resizable() + .aspectRatio(contentMode: .fit) + Image("WireGuardTitle") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: reader.size.width / 2) + Spacer() + } + .frame(maxWidth: .infinity, maxHeight: reader.size.height / 4) + Spacer() + Text("@conradev") + .foregroundStyle(.white) + .font(.body.monospaced()) + } + .padding() + .frame(maxWidth: .infinity) + } + } +} diff --git a/Apple/UI/OAuth2.swift b/Apple/UI/OAuth2.swift new file mode 100644 index 0000000..0fafc8d --- /dev/null +++ b/Apple/UI/OAuth2.swift @@ -0,0 +1,293 @@ +import AuthenticationServices +import Foundation +import os +import SwiftUI + +enum OAuth2 { + enum Error: Swift.Error { + case unknown + case invalidAuthorizationURL + case invalidCallbackURL + case invalidRedirectURI + } + + struct Credential { + var accessToken: String + var refreshToken: String? + var expirationDate: Date? + } + + struct Session { + var authorizationEndpoint: URL + var tokenEndpoint: URL + var redirectURI: URL + var responseType = OAuth2.ResponseType.code + var scopes: Set + var clientID: String + var clientSecret: String + + fileprivate static let queue: OSAllocatedUnfairLock<[Int: CheckedContinuation]> = { + .init(initialState: [:]) + }() + + fileprivate static func handle(url: URL) { + let continuations = queue.withLock { continuations in + let copy = continuations + continuations.removeAll() + return copy + } + for (_, continuation) in continuations { + continuation.resume(returning: url) + } + } + + init( + authorizationEndpoint: URL, + tokenEndpoint: URL, + redirectURI: URL, + scopes: Set, + clientID: String, + clientSecret: String + ) { + self.authorizationEndpoint = authorizationEndpoint + self.tokenEndpoint = tokenEndpoint + self.redirectURI = redirectURI + self.scopes = scopes + self.clientID = clientID + self.clientSecret = clientSecret + } + + private var authorizationURL: URL { + get throws { + var queryItems: [URLQueryItem] = [ + .init(name: "client_id", value: clientID), + .init(name: "response_type", value: responseType.rawValue), + .init(name: "redirect_uri", value: redirectURI.absoluteString) + ] + if !scopes.isEmpty { + queryItems.append(.init(name: "scope", value: scopes.joined(separator: ","))) + } + guard var components = URLComponents(url: authorizationEndpoint, resolvingAgainstBaseURL: false) else { + throw OAuth2.Error.invalidAuthorizationURL + } + components.queryItems = queryItems + guard let authorizationURL = components.url else { throw OAuth2.Error.invalidAuthorizationURL } + return authorizationURL + } + } + + private func handle(callbackURL: URL) async throws -> OAuth2.AccessTokenResponse { + switch responseType { + case .code: + guard let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false) else { + throw OAuth2.Error.invalidCallbackURL + } + return try await handle(response: try components.decode(OAuth2.CodeResponse.self)) + default: + throw OAuth2.Error.invalidCallbackURL + } + } + + private func handle(response: OAuth2.CodeResponse) async throws -> OAuth2.AccessTokenResponse { + var components = URLComponents() + components.queryItems = [ + .init(name: "client_id", value: clientID), + .init(name: "client_secret", value: clientSecret), + .init(name: "grant_type", value: GrantType.authorizationCode.rawValue), + .init(name: "code", value: response.code), + .init(name: "redirect_uri", value: redirectURI.absoluteString) + ] + let httpBody = Data(components.percentEncodedQuery!.utf8) + + var request = URLRequest(url: tokenEndpoint) + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + request.httpMethod = "POST" + request.httpBody = httpBody + + let session = URLSession(configuration: .ephemeral) + let (data, _) = try await session.data(for: request) + return try OAuth2.decoder.decode(OAuth2.AccessTokenResponse.self, from: data) + } + + func authorize(_ session: WebAuthenticationSession) async throws -> Credential { + let authorizationURL = try authorizationURL + let callbackURL = try await session.start( + url: authorizationURL, + redirectURI: redirectURI + ) + return try await handle(callbackURL: callbackURL).credential + } + } + + private struct CodeResponse: Codable { + var code: String + var state: String? + } + + private struct AccessTokenResponse: Codable { + var accessToken: String + var tokenType: TokenType + var expiresIn: Double? + var refreshToken: String? + + var credential: Credential { + .init( + accessToken: accessToken, + refreshToken: refreshToken, + expirationDate: expiresIn.map { Date(timeIntervalSinceNow: $0) } + ) + } + } + + enum TokenType: Codable, RawRepresentable { + case bearer + case unknown(String) + + init(rawValue: String) { + self = switch rawValue.lowercased() { + case "bearer": .bearer + default: .unknown(rawValue) + } + } + + var rawValue: String { + switch self { + case .bearer: "bearer" + case .unknown(let type): type + } + } + } + + enum GrantType: Codable, RawRepresentable { + case authorizationCode + case unknown(String) + + init(rawValue: String) { + self = switch rawValue.lowercased() { + case "authorization_code": .authorizationCode + default: .unknown(rawValue) + } + } + + var rawValue: String { + switch self { + case .authorizationCode: "authorization_code" + case .unknown(let type): type + } + } + } + + enum ResponseType: Codable, RawRepresentable { + case code + case idToken + case unknown(String) + + init(rawValue: String) { + self = switch rawValue.lowercased() { + case "code": .code + case "id_token": .idToken + default: .unknown(rawValue) + } + } + + var rawValue: String { + switch self { + case .code: "code" + case .idToken: "id_token" + case .unknown(let type): type + } + } + } + + fileprivate static var decoder: JSONDecoder { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + return decoder + } + + fileprivate static var encoder: JSONEncoder { + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + return encoder + } +} + +extension WebAuthenticationSession: @unchecked @retroactive Sendable { +} + +extension WebAuthenticationSession { +#if canImport(BrowserEngineKit) + @available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) + fileprivate static func callback(for redirectURI: URL) throws -> ASWebAuthenticationSession.Callback { + switch redirectURI.scheme { + case "https": + guard let host = redirectURI.host else { throw OAuth2.Error.invalidRedirectURI } + return .https(host: host, path: redirectURI.path) + case "http": + throw OAuth2.Error.invalidRedirectURI + case .some(let scheme): + return .customScheme(scheme) + case .none: + throw OAuth2.Error.invalidRedirectURI + } + } +#endif + + fileprivate func start(url: URL, redirectURI: URL) async throws -> URL { + #if canImport(BrowserEngineKit) + if #available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) { + return try await authenticate( + using: url, + callback: try Self.callback(for: redirectURI), + additionalHeaderFields: [:] + ) + } + #endif + + return try await withThrowingTaskGroup(of: URL.self) { group in + group.addTask { + return try await authenticate(using: url, callbackURLScheme: redirectURI.scheme ?? "") + } + + let id = Int.random(in: 0.. some View { + onOpenURL { url in OAuth2.Session.handle(url: url) } + } +} + +extension URLComponents { + fileprivate func decode(_ type: T.Type) throws -> T { + guard let queryItems else { + throw DecodingError.valueNotFound( + T.self, + .init(codingPath: [], debugDescription: "Missing query items") + ) + } + let data = try OAuth2.encoder.encode(try queryItems.values) + return try OAuth2.decoder.decode(T.self, from: data) + } +} + +extension Sequence where Element == URLQueryItem { + fileprivate var values: [String: String?] { + get throws { + try Dictionary(map { ($0.name, $0.value) }) { _, _ in + throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Duplicate query items")) + } + } + } +} diff --git a/Apple/UI/Tunnel.swift b/Apple/UI/Tunnel.swift new file mode 100644 index 0000000..4ec9320 --- /dev/null +++ b/Apple/UI/Tunnel.swift @@ -0,0 +1,61 @@ +import BurrowConfiguration +import NetworkExtension +import SwiftUI + +protocol Tunnel: Sendable { + @MainActor var status: TunnelStatus { get } + + func start() + func stop() + func enable() +} + +public enum TunnelStatus: Sendable, Equatable, Hashable { + case unknown + case permissionRequired + case disabled + case connecting + case connected(Date) + case disconnecting + case disconnected + case reasserting + case invalid + case configurationReadWriteFailed +} + +struct TunnelKey: EnvironmentKey { + static var defaultValue: any Tunnel { + NetworkExtensionTunnel(bundleIdentifier: Constants.networkExtensionBundleIdentifier) + } +} + +extension EnvironmentValues { + var tunnel: any Tunnel { + get { self[TunnelKey.self] } + set { self[TunnelKey.self] = newValue } + } +} + +#if DEBUG +@Observable +@MainActor +final class PreviewTunnel: Tunnel { + private(set) var status: TunnelStatus = .permissionRequired + + nonisolated func start() { + set(.connected(.now)) + } + + nonisolated func stop() { + set(.disconnected) + } + + nonisolated func enable() { + set(.disconnected) + } + + nonisolated private func set(_ status: TunnelStatus) { + Task { @MainActor in self.status = status } + } +} +#endif diff --git a/Apple/UI/TunnelButton.swift b/Apple/UI/TunnelButton.swift new file mode 100644 index 0000000..d0222d4 --- /dev/null +++ b/Apple/UI/TunnelButton.swift @@ -0,0 +1,73 @@ +import SwiftUI + +struct TunnelButton: View { + @Environment(\.tunnel) + var tunnel: any Tunnel + + private var action: Action? { tunnel.action } + + var body: some View { + Button { + if let action { + tunnel.perform(action) + } + } label: { + Text(action.description) + } + .disabled(action.isDisabled) + .padding(.horizontal) + .buttonStyle(.floating) + } +} + +extension Tunnel { + @MainActor fileprivate var action: TunnelButton.Action? { + switch status { + case .permissionRequired, .invalid: + .enable + case .disabled, .disconnecting, .disconnected: + .start + case .connecting, .connected, .reasserting: + .stop + case .unknown, .configurationReadWriteFailed: + nil + } + } +} + +extension TunnelButton { + fileprivate enum Action { + case enable + case start + case stop + } +} + +extension TunnelButton.Action? { + var description: LocalizedStringKey { + switch self { + case .enable: "Enable" + case .start: "Start" + case .stop: "Stop" + case .none: "Start" + } + } + + var isDisabled: Bool { + if case .none = self { + true + } else { + false + } + } +} + +extension Tunnel { + fileprivate func perform(_ action: TunnelButton.Action) { + switch action { + case .enable: enable() + case .start: start() + case .stop: stop() + } + } +} diff --git a/Apple/UI/TunnelStatusView.swift b/Apple/UI/TunnelStatusView.swift new file mode 100644 index 0000000..15717ec --- /dev/null +++ b/Apple/UI/TunnelStatusView.swift @@ -0,0 +1,37 @@ +import SwiftUI + +struct TunnelStatusView: View { + @Environment(\.tunnel) + var tunnel: any Tunnel + + var body: some View { + Text(tunnel.status.description) + } +} + +extension TunnelStatus: CustomStringConvertible { + public var description: String { + switch self { + case .unknown: + "Unknown" + case .permissionRequired: + "Permission Required" + case .disconnected: + "Disconnected" + case .disabled: + "Disabled" + case .connecting: + "Connecting…" + case .connected: + "Connected" + case .disconnecting: + "Disconnecting…" + case .reasserting: + "Reasserting…" + case .invalid: + "Invalid" + case .configurationReadWriteFailed: + "System Error" + } + } +} diff --git a/Apple/UI/UI.xcconfig b/Apple/UI/UI.xcconfig new file mode 100644 index 0000000..b44d676 --- /dev/null +++ b/Apple/UI/UI.xcconfig @@ -0,0 +1,3 @@ +#include "../Configuration/Framework.xcconfig" + +ENABLE_PREVIEWS = YES diff --git a/Cargo.lock b/Cargo.lock index 7fa5b6c..a5554fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -9,48 +18,326 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "aes" -version = "0.7.5" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", - "opaque-debug", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" +dependencies = [ + "async-stream-impl 0.2.1", + "futures-core", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl 0.3.5", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "async-trait" +version = "0.1.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core 0.3.4", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core 0.4.3", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.4", + "object", + "rustc-demangle", +] [[package]] name = "base64" -version = "0.13.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bindgen" -version = "0.61.0" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a022e58a142a46fea340d68012b9201c094e93ec3d033a944a24f8fd4a4f09a" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", @@ -60,9 +347,32 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", - "syn", + "syn 1.0.109", + "which", +] + +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.77", "which", ] @@ -73,45 +383,107 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "block-buffer" -version = "0.10.3" +name = "bitflags" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "burrow" version = "0.1.0" dependencies = [ + "aead", + "anyhow", + "async-channel", + "async-stream 0.2.1", + "axum 0.7.5", + "base64 0.21.7", + "blake2", + "caps", + "chacha20poly1305", + "clap", + "console", + "console-subscriber", + "dotenv", + "fehler", + "futures", + "hmac", + "hyper-util", + "insta", + "ip_network", + "ip_network_table", + "libsystemd", + "log", + "nix 0.27.1", + "once_cell", + "parking_lot", + "prost 0.13.2", + "prost-types 0.13.2", + "rand", + "rand_core", + "reqwest 0.12.7", + "ring", + "rusqlite", + "rust-ini", + "schemars", + "serde", + "serde_json", "tokio", + "tokio-stream", + "toml", + "tonic 0.12.2", + "tonic-build", + "tower", + "tracing", + "tracing-journald", + "tracing-log 0.1.4", + "tracing-oslog", + "tracing-subscriber", "tun", + "x25519-dalek", ] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.2.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "bzip2" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" dependencies = [ "bzip2-sys", "libc", @@ -129,12 +501,24 @@ dependencies = [ ] [[package]] -name = "cc" -version = "1.0.74" +name = "caps" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b" +dependencies = [ + "libc", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ "jobserver", + "libc", + "shlex", ] [[package]] @@ -153,23 +537,174 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "cipher" -version = "0.3.0" +name = "chacha20" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ - "generic-array", + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", ] [[package]] name = "clang-sys" -version = "1.4.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.8.5", +] + +[[package]] +name = "clap" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "console-api" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787" +dependencies = [ + "futures-core", + "prost 0.12.6", + "prost-types 0.12.6", + "tonic 0.10.2", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7481d4c57092cd1c19dd541b92bdce883de840df30aa5d03fd48a3935c01842e" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures-task", + "hdrhistogram", + "humantime", + "prost-types 0.12.6", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic 0.10.2", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", ] [[package]] @@ -180,9 +715,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -190,37 +725,49 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] -name = "crossbeam-utils" -version = "0.8.12" +name = "crossbeam-channel" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ - "cfg-if", + "crossbeam-utils", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -228,14 +775,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] [[package]] -name = "digest" -version = "0.10.5" +name = "curve25519-dalek" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -243,37 +826,142 @@ dependencies = [ ] [[package]] -name = "either" -version = "1.8.0" +name = "dlv-list" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] [[package]] -name = "fastrand" -version = "1.8.0" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "instant", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "flate2" -version = "1.0.24" +name = "event-listener" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fehler" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5729fe49ba028cd550747b6e62cd3d841beccab5390aa398538c31a2d983635" +dependencies = [ + "fehler-macros", +] + +[[package]] +name = "fehler-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -299,81 +987,167 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] -name = "futures-channel" -version = "0.3.25" +name = "futures" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] -name = "futures-sink" -version = "0.3.25" +name = "futures-executor" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" - -[[package]] -name = "futures-task" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" - -[[package]] -name = "futures-util" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] -name = "glob" -version = "0.3.0" +name = "getrandom" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.15" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap", + "http 0.2.12", + "indexmap 2.5.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -387,10 +1161,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "hex-literal" -version = "0.3.4" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64 0.21.7", + "byteorder", + "flate2", + "nom", + "num-traits", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hmac" @@ -402,10 +1219,30 @@ dependencies = [ ] [[package]] -name = "http" -version = "0.2.8" +name = "home" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -414,40 +1251,69 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.22" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -459,6 +1325,70 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.30", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper 1.4.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -466,17 +1396,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", ] [[package]] -name = "idna" -version = "0.3.0" +name = "hyper-util" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -484,58 +1434,127 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] -name = "instant" -version = "0.1.12" +name = "indexmap" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ - "cfg-if", + "equivalent", + "hashbrown 0.14.5", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "insta" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "serde", + "similar", +] + +[[package]] +name = "ip_network" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" + +[[package]] +name = "ip_network_table" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" +dependencies = [ + "ip_network", + "ip_network_table-deps-treebitmap", +] + +[[package]] +name = "ip_network_table-deps-treebitmap" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" + [[package]] name = "ipnet" -version = "2.5.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -545,49 +1564,154 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", ] [[package]] -name = "log" -version = "0.4.17" +name = "libloading" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", + "windows-targets 0.52.6", ] [[package]] -name = "memchr" -version = "2.5.0" +name = "libsqlite3-sys" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libsystemd" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c592dc396b464005f78a5853555b9f240bc5378bf5221acc4e129910b2678869" +dependencies = [ + "hmac", + "libc", + "log", + "nix 0.27.1", + "nom", + "once_cell", + "serde", + "sha2", + "thiserror", + "uuid", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] -name = "mime" -version = "0.3.16" +name = "memoffset" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miette" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +dependencies = [ + "miette-derive", + "once_cell", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" @@ -597,32 +1721,46 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] -name = "mio" -version = "0.8.5" +name = "miniz_oxide" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.42.0", + "adler2", ] [[package]] -name = "native-tls" -version = "0.2.10" +name = "mio" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -636,56 +1774,92 @@ dependencies = [ [[package]] name = "nix" -version = "0.25.0" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ - "autocfg", - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.7.1", "pin-utils", ] [[package]] -name = "nom" -version = "7.1.1" +name = "nix" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "libc", + "memoffset 0.9.1", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] -name = "num_threads" -version = "0.1.6" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "libc", + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.42" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -696,13 +1870,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -713,17 +1887,61 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.77" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", "vcpkg", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "password-hash" version = "0.4.2" @@ -755,15 +1973,45 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.5.0", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -773,88 +2021,295 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] -name = "platforms" -version = "3.0.1" +name = "poly1305" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8ec293fd25f7fcfeb7c70129241419a62c6200a26a725f680aff07c91d0ed05" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn 2.0.77", +] [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.21" +name = "prost" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", +] + +[[package]] +name = "prost" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +dependencies = [ + "bytes", + "prost-derive 0.13.2", +] + +[[package]] +name = "prost-build" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" +dependencies = [ + "bytes", + "heck", + "itertools 0.13.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.13.2", + "prost-types 0.13.2", + "regex", + "syn 2.0.77", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "prost-derive" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +dependencies = [ + "anyhow", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", +] + +[[package]] +name = "prost-types" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" +dependencies = [ + "prost 0.13.2", +] + +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.0.0", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash 2.0.0", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.6.0" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ - "regex-syntax", + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", ] [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.11.12" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", "hyper-tls", "ipnet", "js-sys", @@ -864,9 +2319,12 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", "tokio", "tokio-native-tls", "tower-service", @@ -877,6 +2335,94 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +dependencies = [ + "bitflags 2.6.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rust-ini" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -884,28 +2430,141 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "ryu" -version = "1.0.11" +name = "rustc-hash" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] -name = "schannel" -version = "0.1.20" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "semver", ] [[package]] -name = "security-framework" -version = "2.7.0" +name = "rustix" +version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ - "bitflags", + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.77", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -914,31 +2573,82 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", ] [[package]] -name = "serde" -version = "1.0.147" +name = "semver" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -952,10 +2662,21 @@ dependencies = [ ] [[package]] -name = "sha1" -version = "0.10.5" +name = "sha-1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -964,9 +2685,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -974,41 +2695,99 @@ dependencies = [ ] [[package]] -name = "shlex" -version = "1.1.0" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] -name = "socket2" -version = "0.4.7" +name = "smallvec" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] -name = "subtle" -version = "2.4.1" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "ssri" +version = "9.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7a2b3c2bc9693bcb40870c4e9b5bf0d79f9cb46273321bf855ec513e919082" +dependencies = [ + "base64 0.21.7", + "digest", + "hex", + "miette", + "sha-1", + "sha2", + "thiserror", + "xxhash-rust", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -1016,211 +2795,577 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.3.0" +name = "syn" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", ] [[package]] name = "time" -version = "0.3.16" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ - "itoa", - "libc", - "num_threads", + "deranged", + "num-conv", + "powerfmt", "serde", "time-core", - "time-macros", ] [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] -name = "time-macros" -version = "0.2.5" +name = "tiny-keccak" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ - "time-core", + "crunchy", ] [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.21.2" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "memchr", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] -name = "tokio-util" -version = "0.7.4" +name = "tokio-rustls" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap 2.5.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream 0.3.5", + "async-trait", + "axum 0.6.20", + "base64 0.21.7", + "bytes", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-timeout 0.4.1", + "percent-encoding", + "pin-project", + "prost 0.12.6", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", "tracing", ] [[package]] -name = "tower-service" -version = "0.3.2" +name = "tonic" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" +dependencies = [ + "async-stream 0.3.5", + "async-trait", + "axum 0.7.5", + "base64 0.22.1", + "bytes", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-timeout 0.5.1", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.2", + "socket2", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4ee8877250136bd7e3d2331632810a4df4ea5e004656990d8d66d2f5ee8a67" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] [[package]] -name = "tracing-core" -version = "0.1.30" +name = "tracing-attributes" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "once_cell", + "proc-macro2", + "quote", + "syn 2.0.77", ] [[package]] -name = "try-lock" -version = "0.2.3" +name = "tracing-core" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-journald" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba316a74e8fc3c3896a850dba2375928a9fa171b085ecddfc7c054d39970f3fd" +dependencies = [ + "libc", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-oslog" +version = "0.1.2" +source = "git+https://github.com/Stormshield-robinc/tracing-oslog#c4d21a95e70cdd62b1cea04fc4f8be1c547cad6c" +dependencies = [ + "bindgen 0.64.0", + "cc", + "cfg-if", + "fnv", + "once_cell", + "parking_lot", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log 0.2.0", +] + +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tun" version = "0.1.0" dependencies = [ "anyhow", - "bindgen", - "hex-literal", + "bindgen 0.65.1", + "byteorder", + "fehler", + "futures", + "lazy_static", "libc", - "libloading", - "nix", - "platforms", - "reqwest", - "sha2", + "libloading 0.7.4", + "log", + "nix 0.26.4", + "reqwest 0.11.27", + "schemars", + "serde", "socket2", + "ssri", + "tempfile", "tokio", + "tracing", "widestring", + "windows", "zip", ] -[[package]] -name = "tun-async" -version = "0.1.0" -dependencies = [ - "tun", -] - [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] -name = "url" -version = "2.3.1" +name = "unicode-width" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1229,17 +3374,16 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -1251,34 +3395,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -1288,9 +3433,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1298,49 +3443,59 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "which" -version = "4.3.0" +name = "webpki-roots" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -1365,119 +3520,275 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.36.1" +name = "windows" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", ] [[package]] name = "zip" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ "aes", "byteorder", @@ -1514,10 +3825,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", - "libc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 8afa305..362ba2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,9 @@ [workspace] -members = [ - "burrow", - "tun-async", - "tun" -] +members = ["burrow", "tun"] +resolver = "2" +exclude = ["burrow-gtk"] + +[profile.release] +lto = true +panic = "abort" +opt-level = "z" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..404179b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,99 @@ +FROM docker.io/library/rust:1.79-slim-bookworm AS builder + +ARG TARGETPLATFORM +ARG LLVM_VERSION=16 + +ENV KEYRINGS /etc/apt/keyrings + +RUN set -eux && \ + mkdir -p $KEYRINGS && \ + apt-get update && \ + apt-get install --no-install-recommends -y gpg curl busybox make musl-dev && \ + curl --proto '=https' --tlsv1.2 -sSf https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor --output $KEYRINGS/llvm.gpg && \ + echo "deb [signed-by=$KEYRINGS/llvm.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list && \ + apt-get update && \ + apt-get install --no-install-recommends -y clang-$LLVM_VERSION llvm-$LLVM_VERSION lld-$LLVM_VERSION build-essential sqlite3 libsqlite3-dev musl musl-tools musl-dev protobuf-compiler libprotobuf-dev && \ + ln -s clang-$LLVM_VERSION /usr/bin/clang && \ + ln -s clang /usr/bin/clang++ && \ + ln -s lld-$LLVM_VERSION /usr/bin/ld.lld && \ + ln -s clang-$LLVM_VERSION /usr/bin/clang-cl && \ + ln -s llvm-ar-$LLVM_VERSION /usr/bin/llvm-lib && \ + ln -s lld-link-$LLVM_VERSION /usr/bin/lld-link && \ + update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100 && \ + update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100 && \ + apt-get remove -y --auto-remove && \ + rm -rf /var/lib/apt/lists/* + +RUN case $TARGETPLATFORM in \ + "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl ;; \ + "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl ;; \ + *) exit 1 ;; \ + esac && \ + rustup target add $LLVM_TARGET + +ARG SQLITE_VERSION=3460000 + +RUN case $TARGETPLATFORM in \ + "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl MUSL_TARGET=aarch64-linux-musl ;; \ + "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl MUSL_TARGET=x86_64-linux-musl ;; \ + *) exit 1 ;; \ + esac && \ + curl --proto '=https' --tlsv1.2 -sSfO https://www.sqlite.org/2024/sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ + tar xf sqlite-autoconf-$SQLITE_VERSION.tar.gz && \ + cd sqlite-autoconf-$SQLITE_VERSION && \ + ./configure --disable-shared --disable-dependency-tracking \ + CC="clang-$LLVM_VERSION -target $LLVM_TARGET" \ + CFLAGS="-I/usr/local/include -I/usr/include/$MUSL_TARGET" \ + LDFLAGS="-L/usr/local/lib -L/usr/lib/$MUSL_TARGET -L/lib/$MUSL_TARGET" && \ + make && \ + make install && \ + cd .. && \ + rm -rf sqlite-autoconf-$SQLITE_VERSION sqlite-autoconf-$SQLITE_VERSION.tar.gz + +ENV CC_x86_64_unknown_linux_musl=clang-$LLVM_VERSION \ + AR_x86_64_unknown_linux_musl=llvm-ar-$LLVM_VERSION \ + CC_aarch64_unknown_linux_musl=clang-$LLVM_VERSION \ + AR_aarch64_unknown_linux_musl=llvm-ar-$LLVM_VERSION \ + CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-L/usr/lib/x86_64-linux-musl -L/lib/x86_64-linux-musl -C linker=rust-lld" \ + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-L/usr/lib/aarch64-linux-musl -L/lib/aarch64-linux-musl -C linker=rust-lld" \ + SQLITE3_STATIC=1 \ + SQLITE3_INCLUDE_DIR=/usr/local/include \ + SQLITE3_LIB_DIR=/usr/local/lib + +COPY . . + +RUN case $TARGETPLATFORM in \ + "linux/arm64") LLVM_TARGET=aarch64-unknown-linux-musl ;; \ + "linux/amd64") LLVM_TARGET=x86_64-unknown-linux-musl ;; \ + *) exit 1 ;; \ + esac && \ + cargo install --path burrow --target $LLVM_TARGET + +WORKDIR /tmp/rootfs + +RUN set -eux && \ + mkdir -p ./bin ./etc ./tmp ./data && \ + mv /usr/local/cargo/bin/burrow ./bin/burrow && \ + cp /bin/busybox ./bin/busybox && \ + echo 'burrow:x:10001:10001::/tmp:/bin/busybox' > ./etc/passwd && \ + echo 'burrow:x:10001:' > ./etc/group && \ + chown -R 10001:10001 ./tmp ./data && \ + chmod 0777 ./tmp + +FROM scratch as runtime +LABEL \ + # https://github.com/opencontainers/image-spec/blob/master/annotations.md + org.opencontainers.image.title="burrow" \ + org.opencontainers.image.description="Burrow is an open source tool for burrowing through firewalls, built by teenagers at Hack Club." \ + org.opencontainers.image.url="https://github.com/hackclub/burrow" \ + org.opencontainers.image.source="https://github.com/hackclub/burrow" \ + org.opencontainers.image.vendor="hackclub" \ + org.opencontainers.image.licenses="GPL-3.0" + +USER 10001:10001 +COPY --from=builder /tmp/rootfs / +WORKDIR /data + +EXPOSE 8080 + +CMD ["/bin/burrow", "auth-server"] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2fb2e74 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,675 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom +to share and change all versions of a program--to make sure it remains +free software for all its users. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read . diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6563ab1 --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +tun := $(shell ifconfig -l | sed 's/ /\n/g' | grep utun | tail -n 1) +cargo_console := RUST_BACKTRACE=1 RUST_LOG=debug RUSTFLAGS='--cfg tokio_unstable' cargo run --all-features +cargo_norm := RUST_BACKTRACE=1 RUST_LOG=debug cargo run + +check: + @cargo check + +build: + @cargo run build + +daemon-console: + @$(cargo_console) daemon + +daemon: + @$(cargo_norm) daemon + +start: + @$(cargo_norm) start + +stop: + @$(cargo_norm) stop + +status: + @$(cargo_norm) server-status + +tunnel-config: + @$(cargo_norm) tunnel-config + +test-dns: + @sudo route delete 8.8.8.8 + @sudo route add 8.8.8.8 -interface $(tun) + @dig @8.8.8.8 hackclub.com + +test-https: + @sudo route delete 193.183.0.162 + @sudo route add 193.183.0.162 -interface $(tun) + @curl -vv https://search.marginalia.nu + +v4_target := 146.190.62.39 +test-http: + @sudo route delete ${v4_target} + @sudo route add ${v4_target} -interface $(tun) + @curl -vv ${v4_target}:80 + +test-ipv4: + @sudo route delete ${v4_target} + @sudo route add ${v4_target} -interface $(tun) + @ping ${v4_target} + +v6_target := 2001:4860:4860::8888 +test-ipv6: + @sudo route delete ${v6_target} + @sudo route -n add -inet6 ${v6_target} -interface $(tun) + @echo preparing + @sudo ping6 -v ${v6_target} diff --git a/README.md b/README.md index 9a89463..89914d0 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,41 @@ ![License](https://img.shields.io/github/license/hackclub/burrow) ![Apple Build Status](https://img.shields.io/github/actions/workflow/status/hackclub/burrow/build-apple.yml?branch=main&label=macos%2C%20ios&logo=Apple) ![Crate Build Status](https://img.shields.io/github/actions/workflow/status/hackclub/burrow/build-rust.yml?branch=main&label=crate&logo=Rust) -Burrow is a tool for burrowing through firewalls, built by teenagers at [Hack Club](https://hackclub.com/). +Burrow is an open source tool for burrowing through firewalls, built by teenagers at [Hack Club](https://hackclub.com/). -At its core, `burrow` is a command line utility written in Rust that can open virtual interfaces and direct traffic through them. +`burrow` provides a simple command-line tool to open virtual interfaces and direct traffic through them. -You can use it directly from the command line, or you can use it wrapped inside of an app. Currently, there are apps for iOS and macOS that you can use to start the tunnel. +## Contributing + +Burrow is fully open source, you can fork the repo and start contributing easily. For more information and in-depth discussions, visit the `#burrow` channel on the [Hack Club Slack](https://hackclub.com/slack/), here you can ask for help and talk with other people interested in burrow! Checkout [GETTING_STARTED.md](./docs/GETTING_STARTED.md) for build instructions and [GTK_APP.md](./docs/GTK_APP.md) for the Linux app. + +The project structure is divided in the following folders: + +``` +Apple/ # Xcode project for burrow on macOS and iOS +burrow/ # Higher-level API library for tun and tun-async +burrow-gtk/ # GTK project for burrow on Linux +tun/ # Low-level interface to OS networking + src/ + tokio/ # Async/Tokio code + unix/ # macOS and Linux code + windows/ # Windows networking code +``` + +## Installation + +To start burrowing, download the latest release build in the release section. + +## Hack Club + +Hack Club is a global community of high-school hackers from all around the world! Start your own Hack Club, attend an upcoming hackathon or join our online community at [hackclub.com](https://hackclub.com/). + +## License + +Burrow is open source and licensed under the [GNU General Public License v3.0](./LICENSE.md) + +## Contributors + + + + diff --git a/Tools/version.sh b/Tools/version.sh new file mode 100755 index 0000000..fcb3f00 --- /dev/null +++ b/Tools/version.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +export PATH="$PATH:/opt/homebrew/bin:/usr/local/bin:/etc/profiles/per-user/$USER/bin" + +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")"/.. + +TAG_PREFIX="builds/" + +CURRENT_BUILD=$(git tag --points-at HEAD | tail -n 1) +LATEST_BUILD="$TAG_PREFIX$(git tag -l "builds/[0-9]*" | cut -d'/' -f 2 | sort -n | tail -n 1)" + +CURRENT_BUILD_NUMBER=${CURRENT_BUILD#$TAG_PREFIX} +LATEST_BUILD_NUMBER=${LATEST_BUILD#$TAG_PREFIX} +if [[ -z $LATEST_BUILD_NUMBER ]]; then + LATEST_BUILD_NUMBER="0" +fi + +if [[ ! -z $LATEST_BUILD && $(git merge-base --is-ancestor $LATEST_BUILD HEAD) -ne 0 ]]; then + echo "error: HEAD is not descended from build $LATEST_BUILD_NUMBER" >&2 + exit 1 +fi + +BUILD_NUMBER=$LATEST_BUILD_NUMBER + +if [[ $# -gt 0 && "$1" == "increment" ]]; then + NEW_BUILD_NUMBER=$((LATEST_BUILD_NUMBER + 1)) + NEW_TAG="$TAG_PREFIX$NEW_BUILD_NUMBER" + BUILD_NUMBER=$NEW_BUILD_NUMBER + + git tag $NEW_TAG + git push --quiet origin $NEW_TAG + gh release create "$NEW_TAG" -t "Build $BUILD_NUMBER" --verify-tag --generate-notes >/dev/null +fi + +if [[ -z $(grep $BUILD_NUMBER Apple/Configuration/Version.xcconfig 2>/dev/null) ]]; then + echo "CURRENT_PROJECT_VERSION = $BUILD_NUMBER" > Apple/Configuration/Version.xcconfig + git update-index --assume-unchanged Apple/Configuration/Version.xcconfig +fi + +if [[ $# -gt 0 && "$1" == "status" ]]; then + if [[ $CURRENT_BUILD_NUMBER -eq $LATEST_BUILD_NUMBER ]]; then + echo "clean" + else + echo "dirty" + fi + exit 0 +fi + +echo $BUILD_NUMBER diff --git a/burrow-gtk/.cargo/config.toml b/burrow-gtk/.cargo/config.toml new file mode 100644 index 0000000..87e5dd7 --- /dev/null +++ b/burrow-gtk/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.'cfg(unix)'] +runner = "sh -c" diff --git a/burrow-gtk/.gitignore b/burrow-gtk/.gitignore new file mode 100644 index 0000000..caeec17 --- /dev/null +++ b/burrow-gtk/.gitignore @@ -0,0 +1 @@ +.flatpak-builder diff --git a/burrow-gtk/Cargo.lock b/burrow-gtk/Cargo.lock new file mode 100644 index 0000000..6721318 --- /dev/null +++ b/burrow-gtk/Cargo.lock @@ -0,0 +1,3342 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "async-channel" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" +dependencies = [ + "concurrent-queue", + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", + "which", +] + +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.48", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "burrow" +version = "0.1.0" +dependencies = [ + "aead", + "anyhow", + "async-channel", + "base64", + "blake2", + "caps", + "chacha20poly1305", + "clap", + "console", + "fehler", + "futures", + "hmac", + "ip_network", + "ip_network_table", + "libsystemd", + "log", + "nix 0.27.1", + "once_cell", + "parking_lot", + "rand", + "rand_core", + "ring", + "schemars", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-journald", + "tracing-log 0.1.4", + "tracing-oslog", + "tracing-subscriber", + "tun", + "x25519-dalek", +] + +[[package]] +name = "burrow-gtk" +version = "0.1.0" +dependencies = [ + "anyhow", + "burrow", + "gettext-rs", + "glib-build-tools", + "relm4", + "tokio", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cairo-rs" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3603c4028a5e368d09b51c8b624b9a46edcd7c3778284077a6125af73c9f0a" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691d0c66b1fb4881be80a760cb8fe76ea97218312f9dfe2c9cc0f496ca279cb1" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "caps" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b" +dependencies = [ + "libc", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6100bc57b6209840798d95cb2775684849d332f7bd788db2a8c8caf7ef82a41a" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading 0.8.1", +] + +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fehler" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5729fe49ba028cd550747b6e62cd3d841beccab5390aa398538c31a2d983635" +dependencies = [ + "fehler-macros", +] + +[[package]] +name = "fehler-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset 0.9.0", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695d6bc846438c5708b07007537b9274d883373dd30858ca881d7d71b5540717" +dependencies = [ + "bitflags 1.3.2", + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9285ec3c113c66d7d0ab5676599176f1f42f4944ca1b581852215bf5694870cb" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk4" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3abf96408a26e3eddf881a7f893a1e111767137136e347745e8ea6ed12731ff" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk-pixbuf", + "gdk4-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk4-sys" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc92aa1608c089c49393d014c38ac0390d01e4841e1fedaa75dbcef77aaed64" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gettext-rs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49ea8a8fad198aaa1f9655a2524b64b70eb06b2f3ff37da407566c93054f364" +dependencies = [ + "gettext-sys", + "locale_config", +] + +[[package]] +name = "gettext-sys" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c63ce2e00f56a206778276704bbe38564c8695249fdc8f354b4ef71c57c3839d" +dependencies = [ + "cc", + "temp-dir", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "gio" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6973e92937cf98689b6a054a9e56c657ed4ff76de925e36fc331a15f0c5d30a" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ccf87c30a12c469b6d958950f6a9c09f2be20b7773f7e70d20b867fdf2628c3" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fad45ba8d4d2cea612b432717e834f48031cd8853c8aaf43b2c79fec8d144b" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-build-tools" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3431c56f463443cba9bc3600248bc6d680cb614c2ee1cdd39dab5415bd12ac5c" + +[[package]] +name = "glib-macros" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca5c79337338391f1ab8058d6698125034ce8ef31b72a442437fa6c8580de26" +dependencies = [ + "anyhow", + "heck", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "glib-sys" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d80aa6ea7bba0baac79222204aa786a6293078c210abe69ef1336911d4bdc4f0" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gobject-sys" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd34c3317740a6358ec04572c1bcfd3ac0b5b6529275fae255b237b314bb8062" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "graphene-rs" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def4bb01265b59ed548b05455040d272d989b3012c42d4c1bbd39083cb9b40d9" +dependencies = [ + "glib", + "graphene-sys", + "libc", +] + +[[package]] +name = "graphene-sys" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1856fc817e6a6675e36cea0bd9a3afe296f5d9709d1e2d3182803ac77f0ab21d" +dependencies = [ + "glib-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gsk4" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f01ef44fa7cac15e2da9978529383e6bee03e570ba5bf7036b4c10a15cc3a3c" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk4", + "glib", + "graphene-rs", + "gsk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gsk4-sys" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07a84fb4dcf1323d29435aa85e2f5f58bef564342bef06775ec7bd0da1f01b0" +dependencies = [ + "cairo-sys-rs", + "gdk4-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk4" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b28a32a04cd75cef14a0983f8b0c669e0fe152a0a7725accdeb594e2c764c88b" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "graphene-rs", + "gsk4", + "gtk4-macros", + "gtk4-sys", + "libc", + "once_cell", + "pango", +] + +[[package]] +name = "gtk4-macros" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a4d6b61570f76d3ee542d984da443b1cd69b6105264c61afec3abed08c2500f" +dependencies = [ + "anyhow", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "gtk4-sys" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f8283f707b07e019e76c7f2934bdd4180c277e08aa93f4c0d8dd07b7a34e22f" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "gsk4-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ip_network" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" + +[[package]] +name = "ip_network_table" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" +dependencies = [ + "ip_network", + "ip_network_table-deps-treebitmap", +] + +[[package]] +name = "ip_network_table-deps-treebitmap" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libadwaita" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab9c0843f9f23ff25634df2743690c3a1faffe0a190e60c490878517eb81abf" +dependencies = [ + "bitflags 1.3.2", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "gtk4", + "libadwaita-sys", + "libc", + "pango", +] + +[[package]] +name = "libadwaita-sys" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4231cb2499a9f0c4cdfa4885414b33e39901ddcac61150bc0bb4ff8a57ede404" +dependencies = [ + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk4-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "libsystemd" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c592dc396b464005f78a5853555b9f240bc5378bf5221acc4e129910b2678869" +dependencies = [ + "hmac", + "libc", + "log", + "nix 0.27.1", + "nom", + "once_cell", + "serde", + "sha2", + "thiserror", + "uuid", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "locale_config" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934" +dependencies = [ + "lazy_static", + "objc", + "objc-foundation", + "regex", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miette" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +dependencies = [ + "miette-derive", + "once_cell", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "libc", + "memoffset 0.9.0", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pango" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35be456fc620e61f62dff7ff70fbd54dcbaf0a4b920c0f16de1107c47d921d48" +dependencies = [ + "bitflags 1.3.2", + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da69f9f3850b0d8990d462f8c709561975e95f689c1cdf0fecdebde78b35195" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn 2.0.48", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.4", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relm4" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c16f3fad883034773b7f5af4d7e865532b8f3641e5a8bab2a34561a8d960d81" +dependencies = [ + "async-trait", + "flume", + "fragile", + "futures", + "gtk4", + "libadwaita", + "once_cell", + "relm4-macros", + "tokio", + "tracing", +] + +[[package]] +name = "relm4-macros" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9340e2553c0a184a80a0bfa1dcf73c47f3d48933aa6be90724b202f9fbd24735" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "reqwest" +version = "0.11.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "schemars" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "ssri" +version = "9.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7a2b3c2bc9693bcb40870c4e9b5bf0d79f9cb46273321bf855ec513e919082" +dependencies = [ + "base64", + "digest", + "hex", + "miette", + "sha-1", + "sha2", + "thiserror", + "xxhash-rust", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" + +[[package]] +name = "temp-dir" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd16aa9ffe15fe021c6ee3766772132c6e98dfa395a167e16864f61a9cfb71d6" + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +dependencies = [ + "deranged", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2 0.5.5", + "tokio-macros", + "tracing", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-journald" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba316a74e8fc3c3896a850dba2375928a9fa171b085ecddfc7c054d39970f3fd" +dependencies = [ + "libc", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-oslog" +version = "0.1.2" +source = "git+https://github.com/Stormshield-robinc/tracing-oslog#c4d21a95e70cdd62b1cea04fc4f8be1c547cad6c" +dependencies = [ + "bindgen 0.64.0", + "cc", + "cfg-if", + "fnv", + "once_cell", + "parking_lot", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log 0.2.0", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tun" +version = "0.1.0" +dependencies = [ + "anyhow", + "bindgen 0.65.1", + "byteorder", + "fehler", + "futures", + "lazy_static", + "libc", + "libloading 0.7.4", + "log", + "nix 0.26.4", + "reqwest", + "schemars", + "serde", + "socket2 0.4.10", + "ssri", + "tempfile", + "tokio", + "tracing", + "widestring", + "windows", + "zip", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + +[[package]] +name = "web-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winnow" +version = "0.5.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53be06678ed9e83edb1745eb72efc0bbcd7b5c3c35711a860906aed827a13d61" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/burrow-gtk/Cargo.toml b/burrow-gtk/Cargo.toml new file mode 100644 index 0000000..21cb52e --- /dev/null +++ b/burrow-gtk/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "burrow-gtk" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +relm4 = { version = "0.6", features = ["libadwaita", "gnome_44"]} +burrow = { version = "*", path = "../burrow/" } +tokio = { version = "1.35.0", features = ["time", "sync"] } +gettext-rs = { version = "0.7.0", features = ["gettext-system"] } + +[build-dependencies] +anyhow = "1.0" +glib-build-tools = "0.18.0" diff --git a/burrow-gtk/build-aux/Dockerfile b/burrow-gtk/build-aux/Dockerfile new file mode 100644 index 0000000..834e450 --- /dev/null +++ b/burrow-gtk/build-aux/Dockerfile @@ -0,0 +1,20 @@ +FROM fedora:39 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -eux && \ + dnf update -y && \ + dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib util-linux wget fuse fuse-libs file sqlite sqlite-devel protobuf-compiler protobuf-devel + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal +ENV PATH="/root/.cargo/bin:${PATH}" + +WORKDIR /app +COPY . /app + +ENV SQLITE3_STATIC=1 + +RUN cd /app/burrow-gtk/ && \ + ./build-aux/build_appimage.sh + + diff --git a/burrow-gtk/build-aux/build_appimage.sh b/burrow-gtk/build-aux/build_appimage.sh new file mode 100755 index 0000000..f054cd9 --- /dev/null +++ b/burrow-gtk/build-aux/build_appimage.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -ex + +BURROW_GTK_ROOT="$(readlink -f $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")/..)" +BURROW_GTK_BUILD="$BURROW_GTK_ROOT/build-appimage" +LINUXDEPLOY_VERSION="${LINUXDEPLOY_VERSION:-"1-alpha-20240109-1"}" +BURROW_BUILD_TYPE="${BURROW_BUILD_TYPE:-"release"}" + +if [ "$BURROW_GTK_ROOT" != $(pwd) ]; then + echo "Make sure to cd into burrow-gtk" + exit 1 +fi + +ARCHITECTURE=$(lscpu | grep Architecture | awk '{print $2}') + +if [ "$ARCHITECTURE" == "x86_64" ]; then + wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/$LINUXDEPLOY_VERSION/linuxdeploy-x86_64.AppImage" -o /dev/null -O /tmp/linuxdeploy + chmod a+x /tmp/linuxdeploy +elif [ "$ARCHITECTURE" == "aarch64" ]; then + wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/$LINUXDEPLOY_VERSION/linuxdeploy-aarch64.AppImage" -o /dev/null -O /tmp/linuxdeploy + chmod a+x /tmp/linuxdeploy +fi + + +CFLAGS="-I/usr/local/include -I/usr/include/$MUSL_TARGET -fPIE" +meson setup $BURROW_GTK_BUILD --bindir bin --prefix /usr --buildtype $BURROW_BUILD_TYPE +meson compile -C $BURROW_GTK_BUILD +DESTDIR=AppDir meson install -C $BURROW_GTK_BUILD +cargo b --$BURROW_BUILD_TYPE --manifest-path=../Cargo.toml +/tmp/linuxdeploy --appimage-extract-and-run --appdir $BURROW_GTK_BUILD/AppDir -e $BURROW_GTK_BUILD/../../target/$BURROW_BUILD_TYPE/burrow --output appimage +mv *.AppImage $BURROW_GTK_BUILD diff --git a/burrow-gtk/build-aux/com.hackclub.burrow.devel.json b/burrow-gtk/build-aux/com.hackclub.burrow.devel.json new file mode 100644 index 0000000..4a2e5fc --- /dev/null +++ b/burrow-gtk/build-aux/com.hackclub.burrow.devel.json @@ -0,0 +1,53 @@ +{ + "app-id" : "com.hackclub.burrow-devel", + "runtime" : "org.gnome.Platform", + "runtime-version" : "45", + "sdk" : "org.gnome.Sdk", + "sdk-extensions" : [ + "org.freedesktop.Sdk.Extension.rust-stable" + ], + "command" : "burrow-gtk", + "finish-args" : [ + "--share=network", + "--share=ipc", + "--socket=fallback-x11", + "--device=dri", + "--socket=wayland" + ], + "build-options" : { + "append-path" : "/usr/lib/sdk/rust-stable/bin", + "build-args" : [ + "--share=network" + ], + "env" : { + "RUST_BACKTRACE" : "1", + "RUST_LOG" : "burrow-gtk=debug" + } + }, + "cleanup" : [ + "/include", + "/lib/pkgconfig", + "/man", + "/share/doc", + "/share/gtk-doc", + "/share/man", + "/share/pkgconfig", + "*.la", + "*.a" + ], + "modules" : [ + { + "name" : "burrow-gtk", + "builddir" : true, + "subdir" : "burrow-gtk", + "buildsystem" : "meson", + "config-opts": ["--buildtype=debug"], + "sources" : [ + { + "type": "dir", + "path": "../../" + } + ] + } + ] +} diff --git a/burrow-gtk/build-aux/com.hackclub.burrow.json b/burrow-gtk/build-aux/com.hackclub.burrow.json new file mode 100644 index 0000000..c8b68e5 --- /dev/null +++ b/burrow-gtk/build-aux/com.hackclub.burrow.json @@ -0,0 +1,53 @@ +{ + "app-id" : "com.hackclub.burrow", + "runtime" : "org.gnome.Platform", + "runtime-version" : "45", + "sdk" : "org.gnome.Sdk", + "sdk-extensions" : [ + "org.freedesktop.Sdk.Extension.rust-stable" + ], + "command" : "burrow-gtk", + "finish-args" : [ + "--share=network", + "--share=ipc", + "--socket=fallback-x11", + "--device=dri", + "--socket=wayland" + ], + "build-options" : { + "append-path" : "/usr/lib/sdk/rust-stable/bin", + "build-args" : [ + "--share=network" + ], + "env" : { + "RUST_BACKTRACE" : "1", + "RUST_LOG" : "burrow-gtk=debug" + } + }, + "cleanup" : [ + "/include", + "/lib/pkgconfig", + "/man", + "/share/doc", + "/share/gtk-doc", + "/share/man", + "/share/pkgconfig", + "*.la", + "*.a" + ], + "modules" : [ + { + "name" : "burrow-gtk", + "builddir" : true, + "subdir" : "burrow-gtk", + "buildsystem" : "meson", + "config-opts": ["--buildtype=release"], + "sources" : [ + { + "type": "dir", + "path": "../../" + } + ] + } + ] +} diff --git a/burrow-gtk/build.rs b/burrow-gtk/build.rs new file mode 100644 index 0000000..4db0175 --- /dev/null +++ b/burrow-gtk/build.rs @@ -0,0 +1,16 @@ +use anyhow::Result; + +fn main() -> Result<()> { + compile_gresources()?; + + Ok(()) +} + +fn compile_gresources() -> Result<()> { + glib_build_tools::compile_resources( + &["data"], + "data/resources.gresource.xml", + "compiled.gresource", + ); + Ok(()) +} diff --git a/burrow-gtk/data/app.desktop.in.in b/burrow-gtk/data/app.desktop.in.in new file mode 100644 index 0000000..33b9c5b --- /dev/null +++ b/burrow-gtk/data/app.desktop.in.in @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=@APP_NAME_CAPITALIZED@ +Exec=@APP_NAME@ +Icon=@APP_ID@ +Terminal=false +Type=Application +Categories=GTK;Network +StartupNotify=true diff --git a/burrow-gtk/data/app.gschema.xml.in b/burrow-gtk/data/app.gschema.xml.in new file mode 100644 index 0000000..0541c6f --- /dev/null +++ b/burrow-gtk/data/app.gschema.xml.in @@ -0,0 +1,5 @@ + + + + + diff --git a/burrow-gtk/data/app.metainfo.xml.in b/burrow-gtk/data/app.metainfo.xml.in new file mode 100644 index 0000000..8cc2e59 --- /dev/null +++ b/burrow-gtk/data/app.metainfo.xml.in @@ -0,0 +1,16 @@ + + + @APP_ID@ + CC0 + GPL-3.0-or-later + @APP_NAME_CAPITALIZED@ + @APP_ID@.desktop + + +

No description

+
+ + +

No Summary

+
+
diff --git a/burrow-gtk/data/icons/hicolor/scalable/apps/burrow-gtk.svg b/burrow-gtk/data/icons/hicolor/scalable/apps/burrow-gtk.svg new file mode 100644 index 0000000..a74c4df --- /dev/null +++ b/burrow-gtk/data/icons/hicolor/scalable/apps/burrow-gtk.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + application-x-executable + + + + + + + + + + + + + + + + diff --git a/burrow-gtk/data/icons/hicolor/symbolic/apps/burrow-gtk-symbolic.svg b/burrow-gtk/data/icons/hicolor/symbolic/apps/burrow-gtk-symbolic.svg new file mode 100644 index 0000000..5352e0a --- /dev/null +++ b/burrow-gtk/data/icons/hicolor/symbolic/apps/burrow-gtk-symbolic.svg @@ -0,0 +1 @@ + diff --git a/burrow-gtk/data/meson.build b/burrow-gtk/data/meson.build new file mode 100644 index 0000000..2c3ffd8 --- /dev/null +++ b/burrow-gtk/data/meson.build @@ -0,0 +1,90 @@ +# app.desktop.in.in +desktop_conf = configuration_data() +desktop_conf.set('APP_ID', app_id) +desktop_conf.set('APP_NAME', app_name) +desktop_conf.set('APP_NAME_CAPITALIZED', app_name_capitalized) + +desktop_file_in = configure_file( + input: 'app.desktop.in.in', + output: '@BASENAME@', + configuration: desktop_conf, +) + +desktop_file = i18n.merge_file( + input: desktop_file_in, + output: app_id + '.desktop', + type: 'desktop', + po_dir: '../po', + install: true, + install_dir: datadir / 'applications', +) + +if desktop_file_validate.found() + test( + 'validate-desktop', + desktop_file_validate, + args: [desktop_file], + ) +endif + +# app.gschema.xml.in +gschema_conf = configuration_data() +gschema_conf.set('APP_ID', app_id) +gschema_conf.set('APP_NAME', app_name) +gschema_conf.set('APP_IDPATH', app_idpath) +gschema_file = configure_file( + input: 'app.gschema.xml.in', + output: app_id + '.gschema.xml', + configuration: gschema_conf, + install: true, + install_dir: datadir / 'glib-2.0' / 'schemas', +) + +if glib_compile_schemas.found() + test( + 'validate-gschema', + glib_compile_schemas, + args: [ + '--dry-run', + datadir / 'glib-2.0' / 'schemas', + ], + ) +endif + +# app.metainfo.xml.in +appdata_conf = configuration_data() +appdata_conf.set('APP_ID', app_id) +appdata_conf.set('APP_NAME', app_name) +appdata_conf.set('APP_NAME_CAPITALIZED', app_name_capitalized) +appdata_file_in = configure_file( + input: 'app.metainfo.xml.in', + output: '@BASENAME@', + configuration: appdata_conf, +) +appdata_file = i18n.merge_file( + input: appdata_file_in, + output: app_id + '.metainfo.xml', + po_dir: '../po', + install: true, + install_dir: datadir / 'metainfo', +) + +if appstream_util.found() + test( + 'validate-appdata', + appstream_util, + args: ['validate', '--nonet', appdata_file], + ) +endif + +install_data( + 'icons/hicolor/scalable/apps/' + app_name + '.svg', + install_dir: datadir / 'icons' / 'hicolor' / 'scalable' / 'apps', + rename: app_id + '.svg', +) + +install_data( + 'icons/hicolor/symbolic/apps/' + app_name + '-symbolic.svg', + install_dir: datadir / 'icons' / 'hicolor' / 'symbolic' / 'apps', + rename: app_id + '-symbolic.svg', +) diff --git a/burrow-gtk/data/resources.gresource.xml b/burrow-gtk/data/resources.gresource.xml new file mode 100644 index 0000000..969e77c --- /dev/null +++ b/burrow-gtk/data/resources.gresource.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/burrow-gtk/meson.build b/burrow-gtk/meson.build new file mode 100644 index 0000000..8c2d5c1 --- /dev/null +++ b/burrow-gtk/meson.build @@ -0,0 +1,56 @@ +project( + 'burrow-gtk', + ['rust'], + version: '0.0.1', + meson_version: '>= 1.0', +) + +# Find Cargo +cargo_bin = find_program('cargo') +cargo_env = ['CARGO_HOME=' + meson.project_build_root()] +cargo_opt = ['--manifest-path', meson.project_source_root() / 'Cargo.toml'] +cargo_opt += ['--target-dir', meson.project_build_root() / 'target'] + +# Config +prefix = get_option('prefix') +datadir = prefix / get_option('datadir') +localedir = prefix / get_option('localedir') + +app_name = 'burrow-gtk' +app_name_capitalized = 'Burrow' +base_id = 'com.hackclub.burrow' +app_idpath = '/com/hackclub/' + app_name + '/' +if get_option('buildtype') == 'release' + cargo_opt += ['--release'] + rust_target = 'release' + app_id = base_id +else + rust_target = 'debug' + app_id = base_id + '-' + 'devel' +endif + +# Imports +i18n = import('i18n') +gnome = import('gnome') + +# External Dependencies +dependency('gtk4', version: '>= 4.0') +dependency('libadwaita-1', version: '>= 1.2') + +glib_compile_resources = find_program('glib-compile-resources', required: true) +glib_compile_schemas = find_program('glib-compile-schemas', required: true) +desktop_file_validate = find_program('desktop-file-validate', required: false) +appstream_util = find_program('appstream-util', required: false) +fc_cache = find_program('fc-cache', required: false) + +# Our Sources +subdir('po') +subdir('data') +subdir('src') + +# Gnome Post Install +gnome.post_install( + glib_compile_schemas: true, + gtk_update_icon_cache: true, + update_desktop_database: true, +) diff --git a/burrow-gtk/po/LINGUAS b/burrow-gtk/po/LINGUAS new file mode 100644 index 0000000..e69de29 diff --git a/burrow-gtk/po/POTFILES b/burrow-gtk/po/POTFILES new file mode 100644 index 0000000..08b570f --- /dev/null +++ b/burrow-gtk/po/POTFILES @@ -0,0 +1 @@ +data/app.desktop.in.in diff --git a/burrow-gtk/po/meson.build b/burrow-gtk/po/meson.build new file mode 100644 index 0000000..597577b --- /dev/null +++ b/burrow-gtk/po/meson.build @@ -0,0 +1 @@ +i18n.gettext(app_name, preset: 'glib') diff --git a/burrow-gtk/src/.gitignore b/burrow-gtk/src/.gitignore new file mode 100644 index 0000000..c6bb786 --- /dev/null +++ b/burrow-gtk/src/.gitignore @@ -0,0 +1 @@ +config.rs diff --git a/burrow-gtk/src/components/app.rs b/burrow-gtk/src/components/app.rs new file mode 100644 index 0000000..62c98c0 --- /dev/null +++ b/burrow-gtk/src/components/app.rs @@ -0,0 +1,157 @@ +use super::*; +use anyhow::Context; +use std::time::Duration; + +const RECONNECT_POLL_TIME: Duration = Duration::from_secs(5); + +pub struct App { + daemon_client: Arc>>, + settings_screen: Controller, + switch_screen: AsyncController, +} + +#[derive(Debug)] +pub enum AppMsg { + None, + PostInit, +} + +impl App { + pub fn run() { + let app = RelmApp::new(config::ID); + Self::setup_gresources().unwrap(); + Self::setup_i18n().unwrap(); + + app.run_async::(()); + } + + fn setup_i18n() -> Result<()> { + gettextrs::setlocale(gettextrs::LocaleCategory::LcAll, ""); + gettextrs::bindtextdomain(config::GETTEXT_PACKAGE, config::LOCALEDIR)?; + gettextrs::bind_textdomain_codeset(config::GETTEXT_PACKAGE, "UTF-8")?; + gettextrs::textdomain(config::GETTEXT_PACKAGE)?; + Ok(()) + } + + fn setup_gresources() -> Result<()> { + gtk::gio::resources_register_include!("compiled.gresource") + .context("Failed to register and include compiled gresource.") + } +} + +#[relm4::component(pub, async)] +impl AsyncComponent for App { + type Init = (); + type Input = AppMsg; + type Output = (); + type CommandOutput = (); + + view! { + adw::Window { + set_title: Some("Burrow"), + set_default_size: (640, 480), + } + } + + async fn init( + _: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + let daemon_client = Arc::new(Mutex::new(DaemonClient::new().await.ok())); + + let switch_screen = switch_screen::SwitchScreen::builder() + .launch(switch_screen::SwitchScreenInit { + daemon_client: Arc::clone(&daemon_client), + }) + .forward(sender.input_sender(), |_| AppMsg::None); + + let settings_screen = settings_screen::SettingsScreen::builder() + .launch(settings_screen::SettingsScreenInit { + daemon_client: Arc::clone(&daemon_client), + }) + .forward(sender.input_sender(), |_| AppMsg::None); + + let widgets = view_output!(); + + let view_stack = adw::ViewStack::new(); + view_stack.add_titled(switch_screen.widget(), None, "Switch"); + view_stack.add_titled(settings_screen.widget(), None, "Settings"); + + let view_switcher_bar = adw::ViewSwitcherBar::builder().stack(&view_stack).build(); + view_switcher_bar.set_reveal(true); + + // When libadwaita 1.4 support becomes more avaliable, this approach is more appropriate + // + // let toolbar = adw::ToolbarView::new(); + // toolbar.add_top_bar( + // &adw::HeaderBar::builder() + // .title_widget(>k::Label::new(Some("Burrow"))) + // .build(), + // ); + // toolbar.add_bottom_bar(&view_switcher_bar); + // toolbar.set_content(Some(&view_stack)); + // root.set_content(Some(&toolbar)); + + let content = gtk::Box::new(gtk::Orientation::Vertical, 0); + content.append( + &adw::HeaderBar::builder() + .title_widget(>k::Label::new(Some("Burrow"))) + .build(), + ); + content.append(&view_stack); + content.append(&view_switcher_bar); + + root.set_content(Some(&content)); + + sender.input(AppMsg::PostInit); + + let model = App { + daemon_client, + switch_screen, + settings_screen, + }; + + AsyncComponentParts { model, widgets } + } + + async fn update( + &mut self, + _msg: Self::Input, + _sender: AsyncComponentSender, + _root: &Self::Root, + ) { + loop { + tokio::time::sleep(RECONNECT_POLL_TIME).await; + { + let mut daemon_client = self.daemon_client.lock().await; + let mut disconnected_daemon_client = false; + + if let Some(daemon_client) = daemon_client.as_mut() { + if let Err(_e) = daemon_client.send_command(DaemonCommand::ServerInfo).await { + disconnected_daemon_client = true; + self.switch_screen + .emit(switch_screen::SwitchScreenMsg::DaemonDisconnect); + self.settings_screen + .emit(settings_screen::SettingsScreenMsg::DaemonStateChange) + } + } + + if disconnected_daemon_client || daemon_client.is_none() { + match DaemonClient::new().await { + Ok(new_daemon_client) => { + *daemon_client = Some(new_daemon_client); + self.switch_screen + .emit(switch_screen::SwitchScreenMsg::DaemonReconnect); + self.settings_screen + .emit(settings_screen::SettingsScreenMsg::DaemonStateChange) + } + Err(_e) => { + // TODO: Handle Error + } + } + } + } + } + } +} diff --git a/burrow-gtk/src/components/mod.rs b/burrow-gtk/src/components/mod.rs new file mode 100644 index 0000000..b134809 --- /dev/null +++ b/burrow-gtk/src/components/mod.rs @@ -0,0 +1,21 @@ +use super::*; +use adw::prelude::*; +use burrow::{DaemonClient, DaemonCommand, DaemonResponseData}; +use gtk::Align; +use relm4::{ + component::{ + AsyncComponent, AsyncComponentController, AsyncComponentParts, AsyncComponentSender, + AsyncController, + }, + prelude::*, +}; +use std::sync::Arc; +use tokio::sync::Mutex; + +mod app; +mod settings; +mod settings_screen; +mod switch_screen; + +pub use app::*; +pub use settings::{DaemonGroupMsg, DiagGroupMsg}; diff --git a/burrow-gtk/src/components/settings/daemon_group.rs b/burrow-gtk/src/components/settings/daemon_group.rs new file mode 100644 index 0000000..3817ca6 --- /dev/null +++ b/burrow-gtk/src/components/settings/daemon_group.rs @@ -0,0 +1,111 @@ +use super::*; +use std::process::Command; + +#[derive(Debug)] +pub struct DaemonGroup { + system_setup: SystemSetup, + daemon_client: Arc>>, + already_running: bool, +} + +pub struct DaemonGroupInit { + pub daemon_client: Arc>>, + pub system_setup: SystemSetup, +} + +#[derive(Debug)] +pub enum DaemonGroupMsg { + LaunchLocal, + DaemonStateChange, +} + +#[relm4::component(pub, async)] +impl AsyncComponent for DaemonGroup { + type Init = DaemonGroupInit; + type Input = DaemonGroupMsg; + type Output = (); + type CommandOutput = (); + + view! { + #[name(group)] + adw::PreferencesGroup { + #[watch] + set_sensitive: + (model.system_setup == SystemSetup::AppImage || model.system_setup == SystemSetup::Other) && + !model.already_running, + set_title: "Local Daemon", + set_description: Some("Run Local Daemon"), + + gtk::Button { + set_label: "Launch", + connect_clicked => DaemonGroupMsg::LaunchLocal + } + } + } + + async fn init( + init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + // Should be impossible to panic here + let model = DaemonGroup { + system_setup: init.system_setup, + daemon_client: init.daemon_client.clone(), + already_running: init.daemon_client.lock().await.is_some(), + }; + + let widgets = view_output!(); + + AsyncComponentParts { model, widgets } + } + + async fn update( + &mut self, + msg: Self::Input, + _sender: AsyncComponentSender, + _root: &Self::Root, + ) { + match msg { + DaemonGroupMsg::LaunchLocal => { + let burrow_original_bin = std::env::vars() + .find(|(k, _)| k == "APPDIR") + .map(|(_, v)| v + "/usr/bin/burrow") + .unwrap_or("/usr/bin/burrow".to_owned()); + + let mut burrow_bin = + String::from_utf8(Command::new("mktemp").output().unwrap().stdout).unwrap(); + burrow_bin.pop(); + + let privileged_spawn_script = format!( + r#"TEMP=$(mktemp -p /root) +cp {} $TEMP +chmod +x $TEMP +setcap CAP_NET_BIND_SERVICE,CAP_NET_ADMIN+eip $TEMP +mv $TEMP /tmp/burrow-detached-daemon"#, + burrow_original_bin + ) + .replace('\n', "&&"); + + // TODO: Handle error condition + + Command::new("pkexec") + .arg("sh") + .arg("-c") + .arg(privileged_spawn_script) + .arg(&burrow_bin) + .output() + .unwrap(); + + Command::new("/tmp/burrow-detached-daemon") + .env("RUST_LOG", "debug") + .arg("daemon") + .spawn() + .unwrap(); + } + DaemonGroupMsg::DaemonStateChange => { + self.already_running = self.daemon_client.lock().await.is_some(); + } + } + } +} diff --git a/burrow-gtk/src/components/settings/diag_group.rs b/burrow-gtk/src/components/settings/diag_group.rs new file mode 100644 index 0000000..a15e0ea --- /dev/null +++ b/burrow-gtk/src/components/settings/diag_group.rs @@ -0,0 +1,126 @@ +use super::*; + +#[derive(Debug)] +pub struct DiagGroup { + daemon_client: Arc>>, + + system_setup: SystemSetup, + service_installed: StatusTernary, + socket_installed: StatusTernary, + socket_enabled: StatusTernary, + daemon_running: bool, +} + +pub struct DiagGroupInit { + pub daemon_client: Arc>>, + pub system_setup: SystemSetup, +} + +impl DiagGroup { + async fn new(daemon_client: Arc>>) -> Result { + let system_setup = SystemSetup::new(); + let daemon_running = daemon_client.lock().await.is_some(); + + Ok(Self { + service_installed: system_setup.is_service_installed()?, + socket_installed: system_setup.is_socket_installed()?, + socket_enabled: system_setup.is_socket_enabled()?, + daemon_running, + system_setup, + daemon_client, + }) + } +} + +#[derive(Debug)] +pub enum DiagGroupMsg { + Refresh, +} + +#[relm4::component(pub, async)] +impl AsyncComponent for DiagGroup { + type Init = DiagGroupInit; + type Input = DiagGroupMsg; + type Output = (); + type CommandOutput = (); + + view! { + #[name(group)] + adw::PreferencesGroup { + set_title: "Diagnose", + set_description: Some("Diagnose Burrow"), + + adw::ActionRow { + #[watch] + set_title: &format!("System Type: {}", model.system_setup) + }, + adw::ActionRow { + #[watch] + set_title: &format!( + "Service installed: {}", + status_ternary_to_str(model.service_installed) + ) + }, + adw::ActionRow { + #[watch] + set_title: &format!( + "Socket installed: {}", + status_ternary_to_str(model.socket_installed) + ) + }, + adw::ActionRow { + #[watch] + set_title: &format!( + "Socket enabled: {}", + status_ternary_to_str(model.socket_enabled) + ) + }, + adw::ActionRow { + #[watch] + set_title: &format!( + "Daemon running: {}", + if model.daemon_running { "Yes" } else { "No" } + ) + }, + gtk::Button { + set_label: "Refresh", + connect_clicked => DiagGroupMsg::Refresh + } + } + } + + async fn init( + init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + // Should be impossible to panic here + let model = DiagGroup::new(init.daemon_client).await.unwrap(); + + let widgets = view_output!(); + + AsyncComponentParts { model, widgets } + } + + async fn update( + &mut self, + msg: Self::Input, + _sender: AsyncComponentSender, + _root: &Self::Root, + ) { + match msg { + DiagGroupMsg::Refresh => { + // Should be impossible to panic here + *self = Self::new(Arc::clone(&self.daemon_client)).await.unwrap(); + } + } + } +} + +fn status_ternary_to_str(status: StatusTernary) -> &'static str { + match status { + StatusTernary::True => "Yes", + StatusTernary::False => "No", + StatusTernary::NA => "N/A", + } +} diff --git a/burrow-gtk/src/components/settings/mod.rs b/burrow-gtk/src/components/settings/mod.rs new file mode 100644 index 0000000..aa87db2 --- /dev/null +++ b/burrow-gtk/src/components/settings/mod.rs @@ -0,0 +1,8 @@ +use super::*; +use diag::{StatusTernary, SystemSetup}; + +mod daemon_group; +mod diag_group; + +pub use daemon_group::{DaemonGroup, DaemonGroupInit, DaemonGroupMsg}; +pub use diag_group::{DiagGroup, DiagGroupInit, DiagGroupMsg}; diff --git a/burrow-gtk/src/components/settings_screen.rs b/burrow-gtk/src/components/settings_screen.rs new file mode 100644 index 0000000..971f262 --- /dev/null +++ b/burrow-gtk/src/components/settings_screen.rs @@ -0,0 +1,71 @@ +use super::*; +use diag::SystemSetup; + +pub struct SettingsScreen { + diag_group: AsyncController, + daemon_group: AsyncController, +} + +pub struct SettingsScreenInit { + pub daemon_client: Arc>>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum SettingsScreenMsg { + DaemonStateChange, +} + +#[relm4::component(pub)] +impl SimpleComponent for SettingsScreen { + type Init = SettingsScreenInit; + type Input = SettingsScreenMsg; + type Output = (); + + view! { + #[name(preferences)] + adw::PreferencesPage {} + } + + fn init( + init: Self::Init, + root: &Self::Root, + sender: ComponentSender, + ) -> ComponentParts { + let system_setup = SystemSetup::new(); + + let diag_group = settings::DiagGroup::builder() + .launch(settings::DiagGroupInit { + system_setup, + daemon_client: Arc::clone(&init.daemon_client), + }) + .forward(sender.input_sender(), |_| { + SettingsScreenMsg::DaemonStateChange + }); + + let daemon_group = settings::DaemonGroup::builder() + .launch(settings::DaemonGroupInit { + system_setup, + daemon_client: Arc::clone(&init.daemon_client), + }) + .forward(sender.input_sender(), |_| { + SettingsScreenMsg::DaemonStateChange + }); + + let widgets = view_output!(); + widgets.preferences.add(diag_group.widget()); + widgets.preferences.add(daemon_group.widget()); + + let model = SettingsScreen { diag_group, daemon_group }; + + ComponentParts { model, widgets } + } + + fn update(&mut self, _: Self::Input, _sender: ComponentSender) { + // Currently, `SettingsScreenMsg` only has one variant, so the if is ambiguous. + // + // if let SettingsScreenMsg::DaemonStateChange = msg { + self.diag_group.emit(DiagGroupMsg::Refresh); + self.daemon_group.emit(DaemonGroupMsg::DaemonStateChange); + // } + } +} diff --git a/burrow-gtk/src/components/switch_screen.rs b/burrow-gtk/src/components/switch_screen.rs new file mode 100644 index 0000000..f660536 --- /dev/null +++ b/burrow-gtk/src/components/switch_screen.rs @@ -0,0 +1,158 @@ +use super::*; + +pub struct SwitchScreen { + daemon_client: Arc>>, + switch: gtk::Switch, + switch_screen: gtk::Box, + disconnected_banner: adw::Banner, +} + +pub struct SwitchScreenInit { + pub daemon_client: Arc>>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum SwitchScreenMsg { + DaemonReconnect, + DaemonDisconnect, + Start, + Stop, +} + +#[relm4::component(pub, async)] +impl AsyncComponent for SwitchScreen { + type Init = SwitchScreenInit; + type Input = SwitchScreenMsg; + type Output = (); + type CommandOutput = (); + + view! { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_valign: Align::Fill, + + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, + set_margin_all: 5, + set_valign: Align::Start, + + #[name(setup_banner)] + adw::Banner { + set_title: "Burrow is not running!", + }, + }, + + #[name(switch_screen)] + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + set_margin_all: 5, + set_valign: Align::Center, + set_vexpand: true, + + gtk::Label { + set_label: "Burrow Switch", + }, + + #[name(switch)] + gtk::Switch { + set_halign: Align::Center, + set_hexpand: false, + set_vexpand: false, + connect_active_notify => move |switch| + sender.input(if switch.is_active() { SwitchScreenMsg::Start } else { SwitchScreenMsg::Stop }) + }, + } + } + } + + async fn init( + init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + let mut initial_switch_status = false; + let mut initial_daemon_server_down = false; + + if let Some(daemon_client) = init.daemon_client.lock().await.as_mut() { + if let Ok(res) = daemon_client + .send_command(DaemonCommand::ServerInfo) + .await + .as_ref() + { + initial_switch_status = match res.result.as_ref() { + Ok(DaemonResponseData::None) => false, + Ok(DaemonResponseData::ServerInfo(_)) => true, + _ => false, + }; + } else { + initial_daemon_server_down = true; + } + } else { + initial_daemon_server_down = true; + } + + let widgets = view_output!(); + + widgets.switch.set_active(initial_switch_status); + + if initial_daemon_server_down { + *init.daemon_client.lock().await = None; + widgets.switch.set_active(false); + widgets.switch_screen.set_sensitive(false); + widgets.setup_banner.set_revealed(true); + } + + let model = SwitchScreen { + daemon_client: init.daemon_client, + switch: widgets.switch.clone(), + switch_screen: widgets.switch_screen.clone(), + disconnected_banner: widgets.setup_banner.clone(), + }; + + AsyncComponentParts { model, widgets } + } + + async fn update( + &mut self, + msg: Self::Input, + _: AsyncComponentSender, + _root: &Self::Root, + ) { + let mut disconnected_daemon_client = false; + + if let Some(daemon_client) = self.daemon_client.lock().await.as_mut() { + match msg { + Self::Input::Start => { + if let Err(_e) = daemon_client + .send_command(DaemonCommand::Start(Default::default())) + .await + { + disconnected_daemon_client = true; + } + } + Self::Input::Stop => { + if let Err(_e) = daemon_client.send_command(DaemonCommand::Stop).await { + disconnected_daemon_client = true; + } + } + _ => {} + } + } else { + disconnected_daemon_client = true; + } + + if msg == Self::Input::DaemonReconnect { + self.disconnected_banner.set_revealed(false); + self.switch_screen.set_sensitive(true); + } + + if disconnected_daemon_client || msg == Self::Input::DaemonDisconnect { + *self.daemon_client.lock().await = None; + self.switch.set_active(false); + self.switch_screen.set_sensitive(false); + self.disconnected_banner.set_revealed(true); + } + } +} diff --git a/burrow-gtk/src/config.rs.in b/burrow-gtk/src/config.rs.in new file mode 100644 index 0000000..7da2f3f --- /dev/null +++ b/burrow-gtk/src/config.rs.in @@ -0,0 +1,8 @@ +#[allow(unused)] +pub const ID: &str = @ID@; +#[allow(unused)] +pub const VERSION: &str = @VERSION@; +#[allow(unused)] +pub const LOCALEDIR: &str = @LOCALEDIR@; +#[allow(unused)] +pub const GETTEXT_PACKAGE: &str = @GETTEXT_PACKAGE@; diff --git a/burrow-gtk/src/diag.rs b/burrow-gtk/src/diag.rs new file mode 100644 index 0000000..ab4757e --- /dev/null +++ b/burrow-gtk/src/diag.rs @@ -0,0 +1,91 @@ +use super::*; +use std::{fmt::Display, fs, process::Command}; + +const SYSTEMD_SOCKET_LOC: &str = "/etc/systemd/system/burrow.socket"; +const SYSTEMD_SERVICE_LOC: &str = "/etc/systemd/system/burrow.service"; + +// I don't like this type very much. +#[derive(Debug, Clone, Copy)] +pub enum StatusTernary { + True, + False, + NA, +} + +// Realistically, we may not explicitly "support" non-systemd platforms which would simply this +// code greatly. +// Along with replacing [`StatusTernary`] with good old [`bool`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SystemSetup { + Systemd, + AppImage, + Other, +} + +impl SystemSetup { + pub fn new() -> Self { + if is_appimage() { + SystemSetup::AppImage + } else if Command::new("systemctl").arg("--version").output().is_ok() { + SystemSetup::Systemd + } else { + SystemSetup::Other + } + } + + pub fn is_service_installed(&self) -> Result { + match self { + SystemSetup::Systemd => Ok(fs::metadata(SYSTEMD_SERVICE_LOC).is_ok().into()), + SystemSetup::AppImage => Ok(StatusTernary::NA), + SystemSetup::Other => Ok(StatusTernary::NA), + } + } + + pub fn is_socket_installed(&self) -> Result { + match self { + SystemSetup::Systemd => Ok(fs::metadata(SYSTEMD_SOCKET_LOC).is_ok().into()), + SystemSetup::AppImage => Ok(StatusTernary::NA), + SystemSetup::Other => Ok(StatusTernary::NA), + } + } + + pub fn is_socket_enabled(&self) -> Result { + match self { + SystemSetup::Systemd => { + let output = Command::new("systemctl") + .arg("is-enabled") + .arg("burrow.socket") + .output()? + .stdout; + let output = String::from_utf8(output)?; + Ok((output == "enabled\n").into()) + } + SystemSetup::AppImage => Ok(StatusTernary::NA), + SystemSetup::Other => Ok(StatusTernary::NA), + } + } +} + +impl From for StatusTernary { + fn from(value: bool) -> Self { + if value { + StatusTernary::True + } else { + StatusTernary::False + } + } +} + +impl Display for SystemSetup { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + SystemSetup::Systemd => "Systemd", + SystemSetup::AppImage => "AppImage", + SystemSetup::Other => "Other", + }) + } +} + +pub fn is_appimage() -> bool { + std::env::vars().any(|(k, _)| k == "APPDIR") +} diff --git a/burrow-gtk/src/main.rs b/burrow-gtk/src/main.rs new file mode 100644 index 0000000..6f91e2a --- /dev/null +++ b/burrow-gtk/src/main.rs @@ -0,0 +1,11 @@ +use anyhow::Result; + +pub mod components; +mod diag; + +// Generated using meson +mod config; + +fn main() { + components::App::run(); +} diff --git a/burrow-gtk/src/meson.build b/burrow-gtk/src/meson.build new file mode 100644 index 0000000..ed77771 --- /dev/null +++ b/burrow-gtk/src/meson.build @@ -0,0 +1,34 @@ +# config.rs.in +global_conf = configuration_data() +global_conf.set_quoted('ID', app_id) +global_conf.set_quoted('VERSION', meson.project_version()) +global_conf.set_quoted('LOCALEDIR', localedir) +global_conf.set_quoted('GETTEXT_PACKAGE', app_name) +config = configure_file( + input: 'config.rs.in', + output: 'config.rs', + configuration: global_conf, +) + +run_command( + 'cp', + meson.project_build_root() / 'src' / 'config.rs', + meson.project_source_root() / 'src', + check: true, +) + +# Cargo Build +cargo_build = custom_target( + 'cargo-build', + build_by_default: true, + build_always_stale: true, + output: meson.project_name(), + console: true, + install: true, + install_dir: get_option('bindir'), + command: [ + 'env', cargo_env, + cargo_bin, 'build', + cargo_opt, '&&', 'cp', 'target' / rust_target / meson.project_name(), '@OUTPUT@', + ] + ) diff --git a/burrow-server-compose.yml b/burrow-server-compose.yml new file mode 100644 index 0000000..4ba31ee --- /dev/null +++ b/burrow-server-compose.yml @@ -0,0 +1,38 @@ +version: "2.1" +networks: + wg6: + enable_ipv6: true + ipam: + driver: default + config: + - subnet: "aa:bb:cc:de::/64" +services: + burrow: + image: lscr.io/linuxserver/wireguard:latest + privileged: true + container_name: burrow_server + cap_add: + - NET_ADMIN + - SYS_MODULE + environment: + - PUID=1000 + - PGID=1000 + - TZ=Asia/Calcutta + - SERVERURL=wg.burrow.rs + - SERVERPORT=51820 + - PEERS=10 + - PEERDNS=1.1.1.1 + - INTERNAL_SUBNET=10.13.13.0 + - ALLOWEDIPS=0.0.0.0/0, ::/0 + - PERSISTENTKEEPALIVE_PEERS=all + - LOG_CONFS=true #optional + volumes: + - ./config:/config + - /lib/modules:/lib/modules + ports: + - 51820:51820/udp + sysctls: + - net.ipv4.conf.all.src_valid_mark=1 + - net.ipv6.conf.all.disable_ipv6=0 + - net.ipv6.conf.eth0.proxy_ndp=1 + restart: unless-stopped \ No newline at end of file diff --git a/burrow/Cargo.toml b/burrow/Cargo.toml index ec16981..d5e56c1 100644 --- a/burrow/Cargo.toml +++ b/burrow/Cargo.toml @@ -2,9 +2,98 @@ name = "burrow" version = "0.1.0" edition = "2021" +description = "Burrow is an open source tool for burrowing through firewalls, built by teenagers at Hack Club." +license = "GPL-3.0-or-later" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["lib", "staticlib"] [dependencies] -tokio = { version = "1.21", features = ["rt", "macros"] } -tun = { version = "0.1", path = "../tun" } +anyhow = "1.0" +tokio = { version = "1.37", features = [ + "rt", + "macros", + "sync", + "io-util", + "rt-multi-thread", + "signal", + "time", + "tracing", + "fs", +] } +tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] } +clap = { version = "4.4", features = ["derive"] } +tracing = "0.1" +tracing-log = "0.1" +tracing-oslog = { git = "https://github.com/Stormshield-robinc/tracing-oslog" } +tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } +log = "0.4" +serde = { version = "1", features = ["derive"] } +serde_json = "1.0" +blake2 = "0.10" +chacha20poly1305 = "0.10" +rand = "0.8" +rand_core = "0.6" +aead = "0.5" +x25519-dalek = { version = "2.0", features = [ + "reusable_secrets", + "static_secrets", +] } +ring = "0.17" +parking_lot = "0.12" +hmac = "0.12" +base64 = "0.21" +fehler = "1.0" +ip_network_table = "0.2" +ip_network = "0.4" +async-channel = "2.1" +schemars = "0.8" +futures = "0.3.28" +once_cell = "1.19" +console-subscriber = { version = "0.2.0", optional = true } +console = "0.15.8" +axum = "0.7.4" +reqwest = { version = "0.12", default-features = false, features = [ + "json", + "rustls-tls", +] } +rusqlite = { version = "0.31.0", features = ["blob"] } +dotenv = "0.15.0" +tonic = "0.12.0" +prost = "0.13.1" +prost-types = "0.13.1" +tokio-stream = "0.1" +async-stream = "0.2" +tower = "0.4.13" +hyper-util = "0.1.6" +toml = "0.8.15" +rust-ini = "0.21.0" + +[target.'cfg(target_os = "linux")'.dependencies] +caps = "0.5" +libsystemd = "0.7" +tracing-journald = "0.3" + +[target.'cfg(target_vendor = "apple")'.dependencies] +nix = { version = "0.27" } +rusqlite = { version = "0.31.0", features = ["bundled", "blob"] } + +[dev-dependencies] +insta = { version = "1.32", features = ["yaml"] } + +[package.metadata.generate-rpm] +assets = [ + { source = "target/release/burrow", dest = "/usr/bin/burrow", mode = "755" }, + { source = "systemd/burrow.service", dest = "/etc/systemd/system/burrow.service", mode = "644" }, + { source = "systemd/burrow.socket", dest = "/etc/systemd/system/burrow.socket", mode = "644" }, +] +post_install_script = "../package/rpm/post_install" +pre_uninstall_script = "../package/rpm/pre_uninstall" + +[features] +tokio-console = ["dep:console-subscriber"] +bundled = ["rusqlite/bundled"] + + +[build-dependencies] +tonic-build = "0.12.0" diff --git a/burrow/build.rs b/burrow/build.rs new file mode 100644 index 0000000..8eea5dc --- /dev/null +++ b/burrow/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("../proto/burrow.proto")?; + Ok(()) +} diff --git a/burrow/src/auth/client.rs b/burrow/src/auth/client.rs new file mode 100644 index 0000000..e9721f3 --- /dev/null +++ b/burrow/src/auth/client.rs @@ -0,0 +1,24 @@ +use std::env::var; + +use anyhow::Result; +use reqwest::Url; + +pub async fn login() -> Result<()> { + let state = "vt :P"; + let nonce = "no"; + + let mut url = Url::parse("https://slack.com/openid/connect/authorize")?; + let mut q = url.query_pairs_mut(); + q.append_pair("response_type", "code"); + q.append_pair("scope", "openid profile email"); + q.append_pair("client_id", &var("CLIENT_ID")?); + q.append_pair("state", state); + q.append_pair("team", &var("SLACK_TEAM_ID")?); + q.append_pair("nonce", nonce); + q.append_pair("redirect_uri", "https://burrow.rs/callback"); + drop(q); + + println!("Continue auth in your browser:\n{}", url.as_str()); + + Ok(()) +} diff --git a/burrow/src/auth/mod.rs b/burrow/src/auth/mod.rs new file mode 100644 index 0000000..c07f47e --- /dev/null +++ b/burrow/src/auth/mod.rs @@ -0,0 +1,2 @@ +pub mod client; +pub mod server; diff --git a/burrow/src/auth/server/db.rs b/burrow/src/auth/server/db.rs new file mode 100644 index 0000000..995e64b --- /dev/null +++ b/burrow/src/auth/server/db.rs @@ -0,0 +1,91 @@ +use anyhow::Result; + +use crate::daemon::rpc::grpc_defs::{Network, NetworkType}; + +pub static PATH: &str = "./server.sqlite3"; + +pub fn init_db() -> Result<()> { + let conn = rusqlite::Connection::open(PATH)?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS user ( + id PRIMARY KEY, + created_at TEXT NOT NULL + )", + (), + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS user_connection ( + user_id INTEGER REFERENCES user(id) ON DELETE CASCADE, + openid_provider TEXT NOT NULL, + openid_user_id TEXT NOT NULL, + openid_user_name TEXT NOT NULL, + access_token TEXT NOT NULL, + refresh_token TEXT, + PRIMARY KEY (openid_provider, openid_user_id) + )", + (), + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS device ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + public_key TEXT NOT NULL, + apns_token TEXT UNIQUE, + user_id INT REFERENCES user(id) ON DELETE CASCADE, + created_at TEXT NOT NULL DEFAULT (datetime('now')) CHECK(created_at IS datetime(created_at)), + ipv4 TEXT NOT NULL UNIQUE, + ipv6 TEXT NOT NULL UNIQUE, + access_token TEXT NOT NULL UNIQUE, + refresh_token TEXT NOT NULL UNIQUE, + expires_at TEXT NOT NULL DEFAULT (datetime('now', '+7 days')) CHECK(expires_at IS datetime(expires_at)) + )", + () + ).unwrap(); + + Ok(()) +} + +pub fn store_connection( + openid_user: super::providers::OpenIdUser, + openid_provider: &str, + access_token: &str, + refresh_token: Option<&str>, +) -> Result<()> { + log::debug!("Storing openid user {:#?}", openid_user); + let conn = rusqlite::Connection::open(PATH)?; + + conn.execute( + "INSERT OR IGNORE INTO user (id, created_at) VALUES (?, datetime('now'))", + (&openid_user.sub,), + )?; + conn.execute( + "INSERT INTO user_connection (user_id, openid_provider, openid_user_id, openid_user_name, access_token, refresh_token) VALUES ( + (SELECT id FROM user WHERE id = ?), + ?, + ?, + ?, + ?, + ? + )", + (&openid_user.sub, &openid_provider, &openid_user.sub, &openid_user.name, access_token, refresh_token), + )?; + + Ok(()) +} + +pub fn store_device( + openid_user: super::providers::OpenIdUser, + openid_provider: &str, + access_token: &str, + refresh_token: Option<&str>, +) -> Result<()> { + log::debug!("Storing openid user {:#?}", openid_user); + let conn = rusqlite::Connection::open(PATH)?; + + // TODO + + Ok(()) +} diff --git a/burrow/src/auth/server/mod.rs b/burrow/src/auth/server/mod.rs new file mode 100644 index 0000000..88b3ff3 --- /dev/null +++ b/burrow/src/auth/server/mod.rs @@ -0,0 +1,62 @@ +pub mod db; +pub mod providers; + +use anyhow::Result; +use axum::{http::StatusCode, routing::post, Router}; +use providers::slack::auth; +use tokio::signal; + +pub async fn serve() -> Result<()> { + db::init_db()?; + + let app = Router::new() + .route("/slack-auth", post(auth)) + .route("/device/new", post(device_new)); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); + log::info!("Starting auth server on port 8080"); + axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) + .await + .unwrap(); + + Ok(()) +} + +async fn device_new() -> StatusCode { + StatusCode::OK +} + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } +} + +// mod db { +// use rusqlite::{Connection, Result}; + +// #[derive(Debug)] +// struct User { +// id: i32, +// created_at: String, +// } +// } diff --git a/burrow/src/auth/server/providers/mod.rs b/burrow/src/auth/server/providers/mod.rs new file mode 100644 index 0000000..36ff0bd --- /dev/null +++ b/burrow/src/auth/server/providers/mod.rs @@ -0,0 +1,8 @@ +pub mod slack; +pub use super::db; + +#[derive(serde::Deserialize, Default, Debug)] +pub struct OpenIdUser { + pub sub: String, + pub name: String, +} diff --git a/burrow/src/auth/server/providers/slack.rs b/burrow/src/auth/server/providers/slack.rs new file mode 100644 index 0000000..581cd1e --- /dev/null +++ b/burrow/src/auth/server/providers/slack.rs @@ -0,0 +1,102 @@ +use anyhow::Result; +use axum::{ + extract::Json, + http::StatusCode, + routing::{get, post}, +}; +use reqwest::header::AUTHORIZATION; +use serde::Deserialize; + +use super::db::store_connection; + +#[derive(Deserialize)] +pub struct SlackToken { + slack_token: String, +} +pub async fn auth(Json(payload): Json) -> (StatusCode, String) { + let slack_user = match fetch_slack_user(&payload.slack_token).await { + Ok(user) => user, + Err(e) => { + log::error!("Failed to fetch Slack user: {:?}", e); + return (StatusCode::UNAUTHORIZED, String::new()); + } + }; + + log::info!( + "Slack user {} ({}) logged in.", + slack_user.name, + slack_user.sub + ); + + let conn = match store_connection(slack_user, "slack", &payload.slack_token, None) { + Ok(user) => user, + Err(e) => { + log::error!("Failed to fetch Slack user: {:?}", e); + return (StatusCode::UNAUTHORIZED, String::new()); + } + }; + + (StatusCode::OK, String::new()) +} + +async fn fetch_slack_user(access_token: &str) -> Result { + let client = reqwest::Client::new(); + let res = client + .get("https://slack.com/api/openid.connect.userInfo") + .header(AUTHORIZATION, format!("Bearer {}", access_token)) + .send() + .await? + .json::() + .await?; + + let res_ok = res + .get("ok") + .and_then(|v| v.as_bool()) + .ok_or(anyhow::anyhow!("Slack user object not ok!"))?; + + if !res_ok { + return Err(anyhow::anyhow!("Slack user object not ok!")); + } + + Ok(serde_json::from_value(res)?) +} + +// async fn fetch_save_slack_user_data(query: Query) -> anyhow::Result<()> { +// let client = reqwest::Client::new(); +// log::trace!("Code was {}", &query.code); +// let mut url = Url::parse("https://slack.com/api/openid.connect.token")?; + +// { +// let mut q = url.query_pairs_mut(); +// q.append_pair("client_id", &var("CLIENT_ID")?); +// q.append_pair("client_secret", &var("CLIENT_SECRET")?); +// q.append_pair("code", &query.code); +// q.append_pair("grant_type", "authorization_code"); +// q.append_pair("redirect_uri", "https://burrow.rs/callback"); +// } + +// let data = client +// .post(url) +// .send() +// .await? +// .json::() +// .await?; + +// if !data.ok { +// return Err(anyhow::anyhow!("Slack code exchange response not ok!")); +// } + +// if let Some(access_token) = data.access_token { +// log::trace!("Access token is {access_token}"); +// let user = slack::fetch_slack_user(&access_token) +// .await +// .map_err(|err| anyhow::anyhow!("Failed to fetch Slack user info {:#?}", err))?; + +// db::store_user(user, access_token, String::new()) +// .map_err(|_| anyhow::anyhow!("Failed to store user in db"))?; + +// Ok(()) +// } else { +// Err(anyhow::anyhow!("Access token not found in response")) +// } +// } diff --git a/burrow/src/daemon/apple.rs b/burrow/src/daemon/apple.rs new file mode 100644 index 0000000..c60f131 --- /dev/null +++ b/burrow/src/daemon/apple.rs @@ -0,0 +1,65 @@ +use std::{ + ffi::{c_char, CStr}, + path::PathBuf, + sync::Arc, + thread, +}; + +use once_cell::sync::OnceCell; +use tokio::{ + runtime::{Builder, Handle}, + sync::Notify, +}; +use tracing::error; + +use crate::daemon::daemon_main; + +static BURROW_NOTIFY: OnceCell> = OnceCell::new(); +static BURROW_HANDLE: OnceCell = OnceCell::new(); + +#[no_mangle] +pub unsafe extern "C" fn spawn_in_process(path: *const c_char, db_path: *const c_char) { + crate::tracing::initialize(); + + let notify = BURROW_NOTIFY.get_or_init(|| Arc::new(Notify::new())); + let handle = BURROW_HANDLE.get_or_init(|| { + let path_buf = if path.is_null() { + None + } else { + Some(PathBuf::from(CStr::from_ptr(path).to_str().unwrap())) + }; + let db_path_buf = if db_path.is_null() { + None + } else { + Some(PathBuf::from(CStr::from_ptr(db_path).to_str().unwrap())) + }; + let sender = notify.clone(); + + let (handle_tx, handle_rx) = tokio::sync::oneshot::channel(); + thread::spawn(move || { + let runtime = Builder::new_multi_thread() + .worker_threads(4) + .enable_all() + .thread_name("burrow-worker") + .build() + .unwrap(); + handle_tx.send(runtime.handle().clone()).unwrap(); + runtime.block_on(async { + let result = daemon_main( + path_buf.as_deref(), + db_path_buf.as_deref(), + Some(sender.clone()), + ) + .await; + if let Err(error) = result.as_ref() { + error!("Burrow thread exited: {}", error); + } + result + }) + }); + handle_rx.blocking_recv().unwrap() + }); + + let receiver = notify.clone(); + handle.block_on(async move { receiver.notified().await }); +} diff --git a/burrow/src/daemon/instance.rs b/burrow/src/daemon/instance.rs new file mode 100644 index 0000000..ce96fa5 --- /dev/null +++ b/burrow/src/daemon/instance.rs @@ -0,0 +1,256 @@ +use std::{ + ops::Deref, + path::{Path, PathBuf}, + sync::Arc, + time::Duration, +}; + +use anyhow::Result; +use rusqlite::Connection; +use tokio::sync::{mpsc, watch, Notify, RwLock}; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{Request, Response, Status as RspStatus}; +use tracing::{debug, info, warn}; +use tun::{tokio::TunInterface, TunOptions}; + +use super::rpc::grpc_defs::{ + networks_server::Networks, + tunnel_server::Tunnel, + Empty, + Network, + NetworkDeleteRequest, + NetworkListResponse, + NetworkReorderRequest, + State as RPCTunnelState, + TunnelConfigurationResponse, + TunnelStatusResponse, +}; +use crate::{ + daemon::rpc::{ + DaemonCommand, + DaemonNotification, + DaemonResponse, + DaemonResponseData, + ServerConfig, + ServerInfo, + }, + database::{ + add_network, + delete_network, + get_connection, + list_networks, + load_interface, + reorder_network, + }, + wireguard::{Config, Interface}, +}; + +#[derive(Debug, Clone)] +enum RunState { + Running, + Idle, +} + +impl RunState { + pub fn to_rpc(&self) -> RPCTunnelState { + match self { + RunState::Running => RPCTunnelState::Running, + RunState::Idle => RPCTunnelState::Stopped, + } + } +} + +#[derive(Clone)] +pub struct DaemonRPCServer { + tun_interface: Arc>>, + wg_interface: Arc>, + config: Arc>, + db_path: Option, + wg_state_chan: (watch::Sender, watch::Receiver), + network_update_chan: (watch::Sender<()>, watch::Receiver<()>), +} + +impl DaemonRPCServer { + pub fn new( + wg_interface: Arc>, + config: Arc>, + db_path: Option<&Path>, + ) -> Result { + Ok(Self { + tun_interface: Arc::new(RwLock::new(None)), + wg_interface, + config, + db_path: db_path.map(|p| p.to_owned()), + wg_state_chan: watch::channel(RunState::Idle), + network_update_chan: watch::channel(()), + }) + } + + pub fn get_connection(&self) -> Result { + get_connection(self.db_path.as_deref()).map_err(proc_err) + } + + async fn set_wg_state(&self, state: RunState) -> Result<(), RspStatus> { + self.wg_state_chan.0.send(state).map_err(proc_err) + } + + async fn get_wg_state(&self) -> RunState { + self.wg_state_chan.1.borrow().to_owned() + } + + async fn notify_network_update(&self) -> Result<(), RspStatus> { + self.network_update_chan.0.send(()).map_err(proc_err) + } +} + +#[tonic::async_trait] +impl Tunnel for DaemonRPCServer { + type TunnelConfigurationStream = ReceiverStream>; + type TunnelStatusStream = ReceiverStream>; + + async fn tunnel_configuration( + &self, + _request: Request, + ) -> Result, RspStatus> { + let (tx, rx) = mpsc::channel(10); + tokio::spawn(async move { + let serv_config = ServerConfig::default(); + tx.send(Ok(TunnelConfigurationResponse { + mtu: serv_config.mtu.unwrap_or(1000), + addresses: serv_config.address, + })) + .await + }); + Ok(Response::new(ReceiverStream::new(rx))) + } + + async fn tunnel_start(&self, _request: Request) -> Result, RspStatus> { + let wg_state = self.get_wg_state().await; + match wg_state { + RunState::Idle => { + let tun_if = TunOptions::new().open()?; + debug!("Setting tun on wg_interface"); + self.tun_interface.write().await.replace(tun_if); + self.wg_interface + .write() + .await + .set_tun_ref(self.tun_interface.clone()) + .await; + debug!("tun set on wg_interface"); + + debug!("Setting tun_interface"); + debug!("tun_interface set: {:?}", self.tun_interface); + + debug!("Cloning wg_interface"); + let tmp_wg = self.wg_interface.clone(); + let run_task = tokio::spawn(async move { + let twlock = tmp_wg.read().await; + twlock.run().await + }); + self.set_wg_state(RunState::Running).await?; + } + + RunState::Running => { + warn!("Got start, but tun interface already up."); + } + } + + return Ok(Response::new(Empty {})); + } + + async fn tunnel_stop(&self, _request: Request) -> Result, RspStatus> { + self.wg_interface.write().await.remove_tun().await; + self.set_wg_state(RunState::Idle).await?; + return Ok(Response::new(Empty {})); + } + + async fn tunnel_status( + &self, + _request: Request, + ) -> Result, RspStatus> { + let (tx, rx) = mpsc::channel(10); + let mut state_rx = self.wg_state_chan.1.clone(); + tokio::spawn(async move { + let cur = state_rx.borrow_and_update().to_owned(); + tx.send(Ok(status_rsp(cur))).await; + loop { + state_rx.changed().await.unwrap(); + let cur = state_rx.borrow().to_owned(); + let res = tx.send(Ok(status_rsp(cur))).await; + if res.is_err() { + eprintln!("Tunnel status channel closed"); + break; + } + } + }); + Ok(Response::new(ReceiverStream::new(rx))) + } +} + +#[tonic::async_trait] +impl Networks for DaemonRPCServer { + type NetworkListStream = ReceiverStream>; + + async fn network_add(&self, request: Request) -> Result, RspStatus> { + let conn = self.get_connection()?; + let network = request.into_inner(); + add_network(&conn, &network).map_err(proc_err)?; + self.notify_network_update().await?; + Ok(Response::new(Empty {})) + } + + async fn network_list( + &self, + _request: Request, + ) -> Result, RspStatus> { + debug!("Mock network_list called"); + let (tx, rx) = mpsc::channel(10); + let conn = self.get_connection()?; + let mut sub = self.network_update_chan.1.clone(); + tokio::spawn(async move { + loop { + let networks = list_networks(&conn) + .map(|res| NetworkListResponse { network: res }) + .map_err(proc_err); + let res = tx.send(networks).await; + if res.is_err() { + eprintln!("Network list channel closed"); + break; + } + sub.changed().await.unwrap(); + } + }); + Ok(Response::new(ReceiverStream::new(rx))) + } + + async fn network_reorder( + &self, + request: Request, + ) -> Result, RspStatus> { + let conn = self.get_connection()?; + reorder_network(&conn, request.into_inner()).map_err(proc_err)?; + self.notify_network_update().await?; + Ok(Response::new(Empty {})) + } + + async fn network_delete( + &self, + request: Request, + ) -> Result, RspStatus> { + let conn = self.get_connection()?; + delete_network(&conn, request.into_inner()).map_err(proc_err)?; + self.notify_network_update().await?; + Ok(Response::new(Empty {})) + } +} + +fn proc_err(err: impl ToString) -> RspStatus { + RspStatus::internal(err.to_string()) +} + +fn status_rsp(state: RunState) -> TunnelStatusResponse { + TunnelStatusResponse { + state: state.to_rpc().into(), + start: None, // TODO: Add timestamp + } +} diff --git a/burrow/src/daemon/mod.rs b/burrow/src/daemon/mod.rs new file mode 100644 index 0000000..f6b973f --- /dev/null +++ b/burrow/src/daemon/mod.rs @@ -0,0 +1,63 @@ +use std::{path::Path, sync::Arc}; + +pub mod apple; +mod instance; +mod net; +pub mod rpc; + +use anyhow::{Error as AhError, Result}; +use instance::DaemonRPCServer; +pub use net::{get_socket_path, DaemonClient}; +pub use rpc::{DaemonCommand, DaemonResponseData, DaemonStartOptions}; +use tokio::{ + net::UnixListener, + sync::{Notify, RwLock}, +}; +use tokio_stream::wrappers::UnixListenerStream; +use tonic::transport::Server; +use tracing::{error, info}; + +use crate::{ + daemon::rpc::grpc_defs::{networks_server::NetworksServer, tunnel_server::TunnelServer}, + database::{get_connection, load_interface}, + wireguard::Interface, +}; + +pub async fn daemon_main( + socket_path: Option<&Path>, + db_path: Option<&Path>, + notify_ready: Option>, +) -> Result<()> { + if let Some(n) = notify_ready { + n.notify_one() + } + let conn = get_connection(db_path)?; + let config = load_interface(&conn, "1")?; + let burrow_server = DaemonRPCServer::new( + Arc::new(RwLock::new(config.clone().try_into()?)), + Arc::new(RwLock::new(config)), + db_path.clone(), + )?; + let spp = socket_path.clone(); + let tmp = get_socket_path(); + let sock_path = spp.unwrap_or(Path::new(tmp.as_str())); + if sock_path.exists() { + std::fs::remove_file(sock_path)?; + } + let uds = UnixListener::bind(sock_path)?; + let serve_job = tokio::spawn(async move { + let uds_stream = UnixListenerStream::new(uds); + let _srv = Server::builder() + .add_service(TunnelServer::new(burrow_server.clone())) + .add_service(NetworksServer::new(burrow_server)) + .serve_with_incoming(uds_stream) + .await?; + Ok::<(), AhError>(()) + }); + + info!("Starting daemon..."); + + tokio::try_join!(serve_job) + .map(|_| ()) + .map_err(|e| e.into()) +} diff --git a/burrow/src/daemon/net/mod.rs b/burrow/src/daemon/net/mod.rs new file mode 100644 index 0000000..eb45335 --- /dev/null +++ b/burrow/src/daemon/net/mod.rs @@ -0,0 +1,11 @@ +#[cfg(target_family = "unix")] +mod unix; + +#[cfg(target_family = "unix")] +pub use unix::{get_socket_path, DaemonClient, Listener}; + +#[cfg(target_os = "windows")] +mod windows; + +#[cfg(target_os = "windows")] +pub use windows::{DaemonClient, Listener}; diff --git a/burrow/src/daemon/net/unix.rs b/burrow/src/daemon/net/unix.rs new file mode 100644 index 0000000..975c470 --- /dev/null +++ b/burrow/src/daemon/net/unix.rs @@ -0,0 +1,244 @@ +#[cfg(target_os = "linux")] +use std::os::fd::{IntoRawFd, RawFd}; +use std::{ffi::OsStr, io, path::Path}; + +use anyhow::{anyhow, Error, Result}; +use fehler::throws; +use tokio::{ + io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, + net::{UnixListener, UnixStream}, +}; +use tracing::{debug, error, info}; + +use crate::daemon::rpc::{ + DaemonCommand, + DaemonMessage, + DaemonNotification, + DaemonRequest, + DaemonResponse, + DaemonResponseData, +}; + +#[cfg(not(target_vendor = "apple"))] +const UNIX_SOCKET_PATH: &str = "/run/burrow.sock"; + +#[cfg(target_vendor = "apple")] +const UNIX_SOCKET_PATH: &str = "burrow.sock"; + +pub fn get_socket_path() -> String { + if std::env::var("BURROW_SOCKET_PATH").is_ok() { + return std::env::var("BURROW_SOCKET_PATH").unwrap(); + } + UNIX_SOCKET_PATH.to_string() +} + +pub struct Listener { + cmd_tx: async_channel::Sender, + rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, + pub inner: UnixListener, +} + +impl Listener { + #[throws] + pub fn new( + cmd_tx: async_channel::Sender, + rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, + ) -> Self { + let socket_path = get_socket_path(); + let path = Path::new(OsStr::new(&socket_path)); + Self::new_with_path(cmd_tx, rsp_rx, sub_chan, path)? + } + + #[throws] + #[cfg(target_os = "linux")] + pub fn new_with_path( + cmd_tx: async_channel::Sender, + rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, + path: &Path, + ) -> Self { + let inner = listener_from_path_or_fd(&path, raw_fd())?; + Self { + cmd_tx, + rsp_rx, + sub_chan, + inner, + } + } + + #[throws] + #[cfg(not(target_os = "linux"))] + pub fn new_with_path( + cmd_tx: async_channel::Sender, + rsp_rx: async_channel::Receiver, + sub_chan: async_channel::Receiver, + path: &Path, + ) -> Self { + let inner = listener_from_path(path)?; + Self { + cmd_tx, + rsp_rx, + inner, + sub_chan, + } + } + + pub async fn run(&self) -> Result<()> { + info!("Waiting for connections..."); + loop { + let (stream, _) = self.inner.accept().await?; + let cmd_tx = self.cmd_tx.clone(); + let rsp_rxc = self.rsp_rx.clone(); + let sub_chan = self.sub_chan.clone(); + tokio::task::spawn(async move { + info!("Got connection: {:?}", stream); + Self::stream(stream, cmd_tx, rsp_rxc, sub_chan).await; + }); + } + } + + async fn stream( + stream: UnixStream, + cmd_tx: async_channel::Sender, + rsp_rxc: async_channel::Receiver, + sub_chan: async_channel::Receiver, + ) { + let mut stream = stream; + let (mut read_stream, mut write_stream) = stream.split(); + let buf_reader = BufReader::new(&mut read_stream); + let mut lines = buf_reader.lines(); + loop { + tokio::select! { + Ok(Some(line)) = lines.next_line() => { + info!("Line: {}", line); + let mut res: DaemonResponse = DaemonResponseData::None.into(); + let req = match serde_json::from_str::(&line) { + Ok(req) => Some(req), + Err(e) => { + res.result = Err(e.to_string()); + error!("Failed to parse request: {}", e); + None + } + }; + + let res = serde_json::to_string(&DaemonMessage::from(res)).unwrap(); + + if let Some(req) = req { + cmd_tx.send(req.command).await.unwrap(); + let res = rsp_rxc.recv().await.unwrap().with_id(req.id); + let mut payload = serde_json::to_string(&DaemonMessage::from(res)).unwrap(); + payload.push('\n'); + info!("Sending response: {}", payload); + write_stream.write_all(payload.as_bytes()).await.unwrap(); + } else { + write_stream.write_all(res.as_bytes()).await.unwrap(); + } + } + Ok(cmd) = sub_chan.recv() => { + info!("Got subscription command: {:?}", cmd); + let msg = DaemonMessage::from(cmd); + let mut payload = serde_json::to_string(&msg).unwrap(); + payload.push('\n'); + write_stream.write_all(payload.as_bytes()).await.unwrap(); + } + } + } + } +} + +#[cfg(target_os = "linux")] +fn raw_fd() -> Option { + if !libsystemd::daemon::booted() { + return None; + } + + match libsystemd::activation::receive_descriptors(false) { + Ok(descriptors) => descriptors.into_iter().map(|d| d.into_raw_fd()).next(), + Err(e) => { + tracing::error!("Failed to receive descriptors: {}", e); + None + } + } +} + +#[throws] +#[cfg(target_os = "linux")] +fn listener_from_path_or_fd(path: &Path, raw_fd: Option) -> UnixListener { + match raw_fd.map(listener_from_fd) { + Some(Ok(listener)) => listener, + _ => listener_from_path(path)?, + } +} + +#[throws] +#[cfg(target_os = "linux")] +fn listener_from_fd(fd: RawFd) -> UnixListener { + use std::os::fd::FromRawFd; + + let listener = unsafe { std::os::unix::net::UnixListener::from_raw_fd(fd) }; + listener.set_nonblocking(true)?; + UnixListener::from_std(listener)? +} + +#[throws] +fn listener_from_path(path: &Path) -> UnixListener { + let error = match UnixListener::bind(path) { + Ok(listener) => return listener, + Err(e) => e, + }; + + match error.kind() { + io::ErrorKind::NotFound => { + if let Some(parent) = path.parent() { + info!("Creating parent directory {:?}", parent); + std::fs::create_dir_all(parent)?; + } + } + io::ErrorKind::AddrInUse => { + info!("Removing existing file"); + match std::fs::remove_file(path) { + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()), + stuff => stuff, + }?; + } + _ => error!("Failed to bind to {:?}: {}", path, error), + } + + UnixListener::bind(path)? +} + +#[derive(Debug)] +pub struct DaemonClient { + stream: UnixStream, +} + +impl DaemonClient { + pub async fn new() -> Result { + let socket_path = get_socket_path(); + let path = Path::new(OsStr::new(&socket_path)); + Self::new_with_path(path).await + } + + pub async fn new_with_path(path: &Path) -> Result { + let stream = UnixStream::connect(path).await?; + Ok(Self { stream }) + } + + pub async fn send_command(&mut self, command: DaemonCommand) -> Result { + let mut command = serde_json::to_string(&DaemonRequest { id: 0, command })?; + command.push('\n'); + + self.stream.write_all(command.as_bytes()).await?; + let buf_reader = BufReader::new(&mut self.stream); + let mut lines = buf_reader.lines(); + let response = lines + .next_line() + .await? + .ok_or(anyhow!("Failed to read response"))?; + debug!("Got raw response: {}", response); + let res: DaemonResponse = serde_json::from_str(&response)?; + Ok(res) + } +} diff --git a/burrow/src/daemon/net/windows.rs b/burrow/src/daemon/net/windows.rs new file mode 100644 index 0000000..5918260 --- /dev/null +++ b/burrow/src/daemon/net/windows.rs @@ -0,0 +1,34 @@ +use anyhow::Result; +use fehler::throws; + +use super::DaemonCommand; +use crate::daemon::DaemonResponse; + +pub struct Listener; + +impl Listener { + pub fn new_with_path( + cmd_tx: async_channel::Sender, + rsp_rx: async_channel::Receiver, + path: &Path, + ) -> Self { + Self + } + + pub async fn run(&self) -> Result<()> { + Ok(()) + } +} + +#[derive(Debug)] +pub struct DaemonClient; + +impl DaemonClient { + pub async fn new() -> Result { + Ok(Self) + } + + pub async fn send_command(&mut self, command: DaemonCommand) -> Result { + unimplemented!("This platform does not currently support daemon mode.") + } +} diff --git a/burrow/src/daemon/rpc/client.rs b/burrow/src/daemon/rpc/client.rs new file mode 100644 index 0000000..862e34c --- /dev/null +++ b/burrow/src/daemon/rpc/client.rs @@ -0,0 +1,31 @@ +use anyhow::Result; +use hyper_util::rt::TokioIo; +use tokio::net::UnixStream; +use tonic::transport::{Endpoint, Uri}; +use tower::service_fn; + +use super::grpc_defs::{networks_client::NetworksClient, tunnel_client::TunnelClient}; +use crate::daemon::get_socket_path; + +pub struct BurrowClient { + pub networks_client: NetworksClient, + pub tunnel_client: TunnelClient, +} + +impl BurrowClient { + #[cfg(any(target_os = "linux", target_vendor = "apple"))] + pub async fn from_uds() -> Result { + let channel = Endpoint::try_from("http://[::]:50051")? // NOTE: this is a hack(?) + .connect_with_connector(service_fn(|_: Uri| async { + let sock_path = get_socket_path(); + Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(sock_path).await?)) + })) + .await?; + let nw_client = NetworksClient::new(channel.clone()); + let tun_client = TunnelClient::new(channel.clone()); + Ok(BurrowClient { + networks_client: nw_client, + tunnel_client: tun_client, + }) + } +} diff --git a/burrow/src/daemon/rpc/grpc_defs.rs b/burrow/src/daemon/rpc/grpc_defs.rs new file mode 100644 index 0000000..f3085ee --- /dev/null +++ b/burrow/src/daemon/rpc/grpc_defs.rs @@ -0,0 +1,5 @@ +pub use burrowgrpc::*; + +mod burrowgrpc { + tonic::include_proto!("burrow"); +} diff --git a/burrow/src/daemon/rpc/mod.rs b/burrow/src/daemon/rpc/mod.rs new file mode 100644 index 0000000..512662c --- /dev/null +++ b/burrow/src/daemon/rpc/mod.rs @@ -0,0 +1,43 @@ +pub mod client; +pub mod grpc_defs; +pub mod notification; +pub mod request; +pub mod response; + +pub use client::BurrowClient; +pub use notification::DaemonNotification; +pub use request::{DaemonCommand, DaemonRequest, DaemonStartOptions}; +pub use response::{DaemonResponse, DaemonResponseData, ServerConfig, ServerInfo}; +use serde::{Deserialize, Serialize}; + +/// The `Message` object contains either a `DaemonRequest` or a `DaemonResponse` to be serialized / deserialized +/// for our IPC communication. Our IPC protocol is based on jsonrpc (https://www.jsonrpc.org/specification#overview), +/// but deviates from it in a few ways: +/// - We differentiate Notifications from Requests explicitly. +/// - We have a "type" field to differentiate between a request, a response, and a notification. +/// - The params field may receive any json value(such as a string), not just an object or an array. +#[derive(Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum DaemonMessage { + Request(DaemonRequest), + Response(DaemonResponse), + Notification(DaemonNotification), +} + +impl From for DaemonMessage { + fn from(request: DaemonRequest) -> Self { + DaemonMessage::Request(request) + } +} + +impl From for DaemonMessage { + fn from(response: DaemonResponse) -> Self { + DaemonMessage::Response(response) + } +} + +impl From for DaemonMessage { + fn from(notification: DaemonNotification) -> Self { + DaemonMessage::Notification(notification) + } +} diff --git a/burrow/src/daemon/rpc/notification.rs b/burrow/src/daemon/rpc/notification.rs new file mode 100644 index 0000000..135b0e4 --- /dev/null +++ b/burrow/src/daemon/rpc/notification.rs @@ -0,0 +1,11 @@ +use rpc::ServerConfig; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::daemon::rpc; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "method", content = "params")] +pub enum DaemonNotification { + ConfigChange(ServerConfig), +} diff --git a/burrow/src/daemon/rpc/request.rs b/burrow/src/daemon/rpc/request.rs new file mode 100644 index 0000000..e9480aa --- /dev/null +++ b/burrow/src/daemon/rpc/request.rs @@ -0,0 +1,42 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use tun::TunOptions; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag="method", content="params")] +pub enum DaemonCommand { + Start(DaemonStartOptions), + ServerInfo, + ServerConfig, + Stop, + ReloadConfig(String), +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] +pub struct DaemonStartOptions { + pub tun: TunOptions, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct DaemonRequest { + pub id: u64, + #[serde(flatten)] + pub command: DaemonCommand, +} + +#[test] +fn test_daemoncommand_serialization() { + insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::Start( + DaemonStartOptions::default() + )) + .unwrap()); + insta::assert_snapshot!( + serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions { + tun: TunOptions { ..TunOptions::default() } + })) + .unwrap() + ); + insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()); + insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::Stop).unwrap()); + insta::assert_snapshot!(serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()) +} diff --git a/burrow/src/daemon/rpc/response.rs b/burrow/src/daemon/rpc/response.rs new file mode 100644 index 0000000..61c9c50 --- /dev/null +++ b/burrow/src/daemon/rpc/response.rs @@ -0,0 +1,128 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use tun::TunInterface; + +use crate::wireguard::Config; + +#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] +pub struct DaemonResponse { + // Error types can't be serialized, so this is the second best option. + pub result: Result, + pub id: u64, +} + +impl DaemonResponse { + pub fn new(result: Result) -> Self { + Self { + result: result.map_err(|e| e.to_string()), + id: 0, + } + } +} + +impl From for DaemonResponse { + fn from(val: DaemonResponseData) -> Self { + DaemonResponse::new(Ok::(val)) + } +} + +impl DaemonResponse { + pub fn with_id(self, id: u64) -> Self { + Self { id, ..self } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct ServerInfo { + pub name: Option, + pub ip: Option, + pub mtu: Option, +} + +impl TryFrom<&TunInterface> for ServerInfo { + type Error = anyhow::Error; + + #[cfg(any(target_os = "linux", target_vendor = "apple"))] + fn try_from(server: &TunInterface) -> anyhow::Result { + Ok(ServerInfo { + name: server.name().ok(), + ip: server.ipv4_addr().ok().map(|ip| ip.to_string()), + mtu: server.mtu().ok(), + }) + } + + #[cfg(not(any(target_os = "linux", target_vendor = "apple")))] + fn try_from(server: &TunInterface) -> anyhow::Result { + Err(anyhow!("Not implemented in this platform")) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct ServerConfig { + pub address: Vec, + pub name: Option, + pub mtu: Option, +} + +impl TryFrom<&Config> for ServerConfig { + type Error = anyhow::Error; + + fn try_from(config: &Config) -> anyhow::Result { + Ok(ServerConfig { + address: config.interface.address.clone(), + name: None, + mtu: config.interface.mtu.map(|mtu| mtu as i32), + }) + } +} + +impl Default for ServerConfig { + fn default() -> Self { + Self { + address: vec!["10.13.13.2".to_string()], // Dummy remote address + name: None, + mtu: None, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "type")] +pub enum DaemonResponseData { + ServerInfo(ServerInfo), + ServerConfig(ServerConfig), + None, +} + +#[test] +fn test_response_serialization() -> anyhow::Result<()> { + insta::assert_snapshot!(serde_json::to_string(&DaemonResponse::new(Ok::< + DaemonResponseData, + String, + >( + DaemonResponseData::None + )))?); + insta::assert_snapshot!(serde_json::to_string(&DaemonResponse::new(Ok::< + DaemonResponseData, + String, + >( + DaemonResponseData::ServerInfo(ServerInfo { + name: Some("burrow".to_string()), + ip: None, + mtu: Some(1500) + }) + )))?); + insta::assert_snapshot!(serde_json::to_string(&DaemonResponse::new(Err::< + DaemonResponseData, + String, + >( + "error".to_string() + )))?); + insta::assert_snapshot!(serde_json::to_string(&DaemonResponse::new(Ok::< + DaemonResponseData, + String, + >( + DaemonResponseData::ServerConfig(ServerConfig::default()) + )))?); + Ok(()) +} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-2.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-2.snap new file mode 100644 index 0000000..01ec8a7 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-2.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions {\n tun: TunOptions { ..TunOptions::default() },\n })).unwrap()" +--- +{"method":"Start","params":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-3.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-3.snap new file mode 100644 index 0000000..a6a0466 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-3.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()" +--- +{"method":"ServerInfo"} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-4.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-4.snap new file mode 100644 index 0000000..f930051 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-4.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::Stop).unwrap()" +--- +{"method":"Stop"} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-5.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-5.snap new file mode 100644 index 0000000..89dc42c --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization-5.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()" +--- +{"method":"ServerConfig"} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization.snap new file mode 100644 index 0000000..aeca659 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__request__daemoncommand_serialization.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/request.rs +expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()" +--- +{"method":"Start","params":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-2.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-2.snap new file mode 100644 index 0000000..d7bd712 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-2.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::ServerInfo(ServerInfo {\n name: Some(\"burrow\".to_string()),\n ip: None,\n mtu: Some(1500),\n }))))?" +--- +{"result":{"Ok":{"type":"ServerInfo","name":"burrow","ip":null,"mtu":1500}},"id":0} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-3.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-3.snap new file mode 100644 index 0000000..30068f3 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-3.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Err::(\"error\".to_string())))?" +--- +{"result":{"Err":"error"},"id":0} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap new file mode 100644 index 0000000..c40db25 --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::ServerConfig(ServerConfig::default()))))?" +--- +{"result":{"Ok":{"type":"ServerConfig","address":["10.13.13.2"],"name":null,"mtu":null}},"id":0} diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization.snap new file mode 100644 index 0000000..31bd84b --- /dev/null +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/rpc/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::None)))?" +--- +{"result":{"Ok":{"type":"None"}},"id":0} diff --git a/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-2.snap b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-2.snap new file mode 100644 index 0000000..f78eeaa --- /dev/null +++ b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-2.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/command.rs +expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions {\n tun: TunOptions { ..TunOptions::default() },\n })).unwrap()" +--- +{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}} diff --git a/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-3.snap b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-3.snap new file mode 100644 index 0000000..80b9e24 --- /dev/null +++ b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-3.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/command.rs +expression: "serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()" +--- +"ServerInfo" diff --git a/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-4.snap b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-4.snap new file mode 100644 index 0000000..8dc1b8b --- /dev/null +++ b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-4.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/command.rs +expression: "serde_json::to_string(&DaemonCommand::Stop).unwrap()" +--- +"Stop" diff --git a/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-5.snap b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-5.snap new file mode 100644 index 0000000..9334ece --- /dev/null +++ b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization-5.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/command.rs +expression: "serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()" +--- +"ServerConfig" diff --git a/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization.snap b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization.snap new file mode 100644 index 0000000..eee563d --- /dev/null +++ b/burrow/src/daemon/snapshots/burrow__daemon__command__daemoncommand_serialization.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/command.rs +expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()" +--- +{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}} diff --git a/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-2.snap b/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-2.snap new file mode 100644 index 0000000..3787cd1 --- /dev/null +++ b/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-2.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::ServerInfo(ServerInfo {\n name: Some(\"burrow\".to_string()),\n ip: None,\n mtu: Some(1500),\n }))))?" +--- +{"result":{"Ok":{"ServerInfo":{"name":"burrow","ip":null,"mtu":1500}}},"id":0} diff --git a/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-3.snap b/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-3.snap new file mode 100644 index 0000000..4ef9575 --- /dev/null +++ b/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-3.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Err::(\"error\".to_string())))?" +--- +{"result":{"Err":"error"},"id":0} diff --git a/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-4.snap b/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-4.snap new file mode 100644 index 0000000..0b9385c --- /dev/null +++ b/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization-4.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::ServerConfig(ServerConfig::default()))))?" +--- +{"result":{"Ok":{"ServerConfig":{"address":["10.13.13.2"],"name":null,"mtu":null}}},"id":0} diff --git a/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization.snap b/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization.snap new file mode 100644 index 0000000..647d01c --- /dev/null +++ b/burrow/src/daemon/snapshots/burrow__daemon__response__response_serialization.snap @@ -0,0 +1,5 @@ +--- +source: burrow/src/daemon/response.rs +expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::None)))?" +--- +{"result":{"Ok":"None"},"id":0} diff --git a/burrow/src/database.rs b/burrow/src/database.rs new file mode 100644 index 0000000..9a9aac3 --- /dev/null +++ b/burrow/src/database.rs @@ -0,0 +1,216 @@ +use std::path::Path; + +use anyhow::Result; +use rusqlite::{params, Connection}; + +use crate::{ + daemon::rpc::grpc_defs::{ + Network as RPCNetwork, + NetworkDeleteRequest, + NetworkReorderRequest, + NetworkType, + }, + wireguard::config::{Config, Interface, Peer}, +}; + +#[cfg(target_vendor = "apple")] +const DB_PATH: &str = "burrow.db"; + +#[cfg(not(target_vendor = "apple"))] +const DB_PATH: &str = "/var/lib/burrow/burrow.db"; + +const CREATE_WG_INTERFACE_TABLE: &str = "CREATE TABLE IF NOT EXISTS wg_interface ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + listen_port INTEGER, + mtu INTEGER, + private_key TEXT NOT NULL, + address TEXT NOT NULL, + dns TEXT NOT NULL +)"; + +const CREATE_WG_PEER_TABLE: &str = "CREATE TABLE IF NOT EXISTS wg_peer ( + interface_id INT REFERENCES wg_interface(id) ON UPDATE CASCADE, + endpoint TEXT NOT NULL, + public_key TEXT NOT NULL, + allowed_ips TEXT NOT NULL, + preshared_key TEXT +)"; + +const CREATE_NETWORK_TABLE: &str = "CREATE TABLE IF NOT EXISTS network ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL, + payload BLOB, + idx INTEGER, + interface_id INT REFERENCES wg_interface(id) ON UPDATE CASCADE +); +CREATE TRIGGER IF NOT EXISTS increment_network_idx +AFTER INSERT ON network +BEGIN + UPDATE network + SET idx = (SELECT COALESCE(MAX(idx), 0) + 1 FROM network) + WHERE id = NEW.id; +END; +"; + +pub fn initialize_tables(conn: &Connection) -> Result<()> { + conn.execute(CREATE_WG_INTERFACE_TABLE, [])?; + conn.execute(CREATE_WG_PEER_TABLE, [])?; + conn.execute(CREATE_NETWORK_TABLE, [])?; + Ok(()) +} + +pub fn load_interface(conn: &Connection, interface_id: &str) -> Result { + let iface = conn.query_row( + "SELECT private_key, dns, address, listen_port, mtu FROM wg_interface WHERE id = ?", + [&interface_id], + |row| { + let dns_rw: String = row.get(1)?; + let dns = parse_lst(&dns_rw); + let address_rw: String = row.get(2)?; + let address = parse_lst(&address_rw); + Ok(Interface { + private_key: row.get(0)?, + dns, + address, + mtu: row.get(4)?, + listen_port: row.get(3)?, + }) + }, + )?; + let mut peers_stmt = conn.prepare("SELECT public_key, preshared_key, allowed_ips, endpoint FROM wg_peer WHERE interface_id = ?")?; + let peers = peers_stmt + .query_map([&interface_id], |row| { + let preshared_key: Option = row.get(1)?; + let allowed_ips_rw: String = row.get(2)?; + let allowed_ips: Vec = + allowed_ips_rw.split(',').map(|s| s.to_string()).collect(); + Ok(Peer { + public_key: row.get(0)?, + preshared_key, + allowed_ips, + endpoint: row.get(3)?, + persistent_keepalive: None, + name: None, + }) + })? + .collect::>>()?; + Ok(Config { interface: iface, peers }) +} + +pub fn dump_interface(conn: &Connection, config: &Config) -> Result<()> { + let mut stmt = conn.prepare("INSERT INTO wg_interface (private_key, dns, address, listen_port, mtu) VALUES (?, ?, ?, ?, ?)")?; + let cif = &config.interface; + stmt.execute(params![ + cif.private_key, + to_lst(&cif.dns), + to_lst(&cif.address), + cif.listen_port.unwrap_or(51820), + cif.mtu + ])?; + let interface_id = conn.last_insert_rowid(); + let mut stmt = conn.prepare("INSERT INTO wg_peer (interface_id, public_key, preshared_key, allowed_ips, endpoint) VALUES (?, ?, ?, ?, ?)")?; + for peer in &config.peers { + stmt.execute(params![ + &interface_id, + &peer.public_key, + &peer.preshared_key, + &peer.allowed_ips.join(","), + &peer.endpoint + ])?; + } + Ok(()) +} + +pub fn get_connection(path: Option<&Path>) -> Result { + let p = path.unwrap_or_else(|| std::path::Path::new(DB_PATH)); + if !p.exists() { + let conn = Connection::open(p)?; + initialize_tables(&conn)?; + dump_interface(&conn, &Config::default())?; + return Ok(conn); + } + Ok(Connection::open(p)?) +} + +pub fn add_network(conn: &Connection, network: &RPCNetwork) -> Result<()> { + let mut stmt = conn.prepare("INSERT INTO network (id, type, payload) VALUES (?, ?, ?)")?; + stmt.execute(params![ + network.id, + network.r#type().as_str_name(), + &network.payload + ])?; + if network.r#type() == NetworkType::WireGuard { + let payload_str = String::from_utf8(network.payload.clone())?; + let wg_config = Config::from_content_fmt(&payload_str, "ini")?; + dump_interface(conn, &wg_config)?; + } + Ok(()) +} + +pub fn list_networks(conn: &Connection) -> Result> { + let mut stmt = conn.prepare("SELECT id, type, payload FROM network ORDER BY idx")?; + let networks: Vec = stmt + .query_map([], |row| { + println!("row: {:?}", row); + let network_id: i32 = row.get(0)?; + let network_type: String = row.get(1)?; + let network_type = NetworkType::from_str_name(network_type.as_str()) + .ok_or(rusqlite::Error::InvalidQuery)?; + let payload: Vec = row.get(2)?; + Ok(RPCNetwork { + id: network_id, + r#type: network_type.into(), + payload: payload.into(), + }) + })? + .collect::, rusqlite::Error>>()?; + Ok(networks) +} + +pub fn reorder_network(conn: &Connection, req: NetworkReorderRequest) -> Result<()> { + let mut stmt = conn.prepare("UPDATE network SET idx = ? WHERE id = ?")?; + let res = stmt.execute(params![req.index, req.id])?; + if res == 0 { + return Err(anyhow::anyhow!("No such network exists")); + } + Ok(()) +} + +pub fn delete_network(conn: &Connection, req: NetworkDeleteRequest) -> Result<()> { + let mut stmt = conn.prepare("DELETE FROM network WHERE id = ?")?; + let res = stmt.execute(params![req.id])?; + if res == 0 { + return Err(anyhow::anyhow!("No such network exists")); + } + Ok(()) +} + +fn parse_lst(s: &str) -> Vec { + if s.is_empty() { + return vec![]; + } + s.split(',').map(|s| s.to_string()).collect() +} + +fn to_lst(v: &Vec) -> String { + v.iter() + .map(|s| s.to_string()) + .collect::>() + .join(",") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_db() { + let conn = Connection::open_in_memory().unwrap(); + initialize_tables(&conn).unwrap(); + let config = Config::default(); + dump_interface(&conn, &config).unwrap(); + let loaded = load_interface(&conn, "1").unwrap(); + assert_eq!(config, loaded); + } +} diff --git a/burrow/src/lib.rs b/burrow/src/lib.rs new file mode 100644 index 0000000..6aae1fb --- /dev/null +++ b/burrow/src/lib.rs @@ -0,0 +1,22 @@ +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +pub mod wireguard; + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +mod daemon; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +pub mod database; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +mod auth; +pub(crate) mod tracing; + +#[cfg(target_vendor = "apple")] +pub use daemon::apple::spawn_in_process; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +pub use daemon::{ + rpc::DaemonResponse, + rpc::ServerInfo, + DaemonClient, + DaemonCommand, + DaemonResponseData, + DaemonStartOptions, +}; diff --git a/burrow/src/main.rs b/burrow/src/main.rs index 3d59fd3..e87b4c9 100644 --- a/burrow/src/main.rs +++ b/burrow/src/main.rs @@ -1,14 +1,296 @@ -use tokio::io::Result; -use tun::TunInterface; +use anyhow::Result; +use clap::{Args, Parser, Subcommand}; -async fn lol() -> Result<()> { - let iface = TunInterface::new()?; - println!("{:?}", iface.name()); +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +mod daemon; +pub(crate) mod tracing; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +mod wireguard; + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +mod auth; + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +use daemon::{DaemonClient, DaemonCommand}; + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +use crate::daemon::DaemonResponseData; + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +pub mod database; + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +use crate::daemon::rpc::{grpc_defs::Empty, BurrowClient}; + +#[derive(Parser)] +#[command(name = "Burrow")] +#[command(author = "Hack Club ")] +#[command(version = "0.1")] +#[command( + about = "Burrow is a tool for burrowing through firewalls, built by teenagers at Hack Club.", + long_about = "Burrow is a 🚀 blazingly fast 🚀 tool designed to penetrate unnecessarily restrictive firewalls, providing teenagers worldwide with secure, less-filtered, and safe access to the internet! +It's being built by teenagers from Hack Club, in public! Check it out: https://github.com/hackclub/burrow +Spotted a bug? Please open an issue! https://github.com/hackclub/burrow/issues/new" +)] + +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Start Burrow + Start(StartArgs), + /// Stop Burrow daemon + Stop, + /// Start Burrow daemon + Daemon(DaemonArgs), + /// Server Info + ServerInfo, + /// Server config + ServerConfig, + /// Reload Config + ReloadConfig(ReloadConfigArgs), + /// Authentication server + AuthServer, + /// Server Status + ServerStatus, + /// Tunnel Config + TunnelConfig, + /// Add Network + NetworkAdd(NetworkAddArgs), + /// List Networks + NetworkList, + /// Reorder Network + NetworkReorder(NetworkReorderArgs), + /// Delete Network + NetworkDelete(NetworkDeleteArgs), +} + +#[derive(Args)] +struct ReloadConfigArgs { + #[clap(long, short)] + interface_id: String, +} + +#[derive(Args)] +struct StartArgs {} + +#[derive(Args)] +struct DaemonArgs {} + +#[derive(Args)] +struct NetworkAddArgs { + id: i32, + network_type: i32, + payload_path: String, +} + +#[derive(Args)] +struct NetworkReorderArgs { + id: i32, + index: i32, +} + +#[derive(Args)] +struct NetworkDeleteArgs { + id: i32, +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_start() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let res = client.tunnel_client.tunnel_start(Empty {}).await?; + println!("Got results! {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_stop() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let res = client.tunnel_client.tunnel_stop(Empty {}).await?; + println!("Got results! {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_serverstatus() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let mut res = client + .tunnel_client + .tunnel_status(Empty {}) + .await? + .into_inner(); + if let Some(st) = res.message().await? { + println!("Server Status: {:?}", st); + } else { + println!("Server Status is None"); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_tun_config() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let mut res = client + .tunnel_client + .tunnel_configuration(Empty {}) + .await? + .into_inner(); + if let Some(config) = res.message().await? { + println!("Tunnel Config: {:?}", config); + } else { + println!("Tunnel Config is None"); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_add(id: i32, network_type: i32, payload_path: &str) -> Result<()> { + use tokio::{fs::File, io::AsyncReadExt}; + + use crate::daemon::rpc::grpc_defs::Network; + + let mut file = File::open(payload_path).await?; + let mut payload = Vec::new(); + file.read_to_end(&mut payload).await?; + + let mut client = BurrowClient::from_uds().await?; + let network = Network { + id, + r#type: network_type, + payload, + }; + let res = client.networks_client.network_add(network).await?; + println!("Network Add Response: {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_list() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let mut res = client + .networks_client + .network_list(Empty {}) + .await? + .into_inner(); + while let Some(network_list) = res.message().await? { + println!("Network List: {:?}", network_list); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_reorder(id: i32, index: i32) -> Result<()> { + use crate::daemon::rpc::grpc_defs::NetworkReorderRequest; + + let mut client = BurrowClient::from_uds().await?; + let reorder_request = NetworkReorderRequest { id, index }; + let res = client + .networks_client + .network_reorder(reorder_request) + .await?; + println!("Network Reorder Response: {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_delete(id: i32) -> Result<()> { + use crate::daemon::rpc::grpc_defs::NetworkDeleteRequest; + + let mut client = BurrowClient::from_uds().await?; + let delete_request = NetworkDeleteRequest { id }; + let res = client + .networks_client + .network_delete(delete_request) + .await?; + println!("Network Delete Response: {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +fn handle_unexpected(res: Result) { + match res { + Ok(DaemonResponseData::None) => { + println!("Server not started.") + } + Ok(res) => { + println!("Unexpected Response: {:?}", res) + } + Err(e) => { + println!("Error when retrieving from server: {}", e) + } + } +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_serverinfo() -> Result<()> { + let mut client = DaemonClient::new().await?; + let res = client.send_command(DaemonCommand::ServerInfo).await?; + if let Ok(DaemonResponseData::ServerInfo(si)) = res.result { + println!("Got Result! {:?}", si); + } else { + handle_unexpected(res.result); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_serverconfig() -> Result<()> { + let mut client = DaemonClient::new().await?; + let res = client.send_command(DaemonCommand::ServerConfig).await?; + if let Ok(DaemonResponseData::ServerConfig(cfig)) = res.result { + println!("Got Result! {:?}", cfig); + } else { + handle_unexpected(res.result); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_reloadconfig(interface_id: String) -> Result<()> { + let mut client = DaemonClient::new().await?; + let res = client + .send_command(DaemonCommand::ReloadConfig(interface_id)) + .await?; + if let Ok(DaemonResponseData::ServerConfig(cfig)) = res.result { + println!("Got Result! {:?}", cfig); + } else { + handle_unexpected(res.result); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[tokio::main] +async fn main() -> Result<()> { + tracing::initialize(); + dotenv::dotenv().ok(); + + let cli = Cli::parse(); + match &cli.command { + Commands::Start(..) => try_start().await?, + Commands::Stop => try_stop().await?, + Commands::Daemon(_) => daemon::daemon_main(None, None, None).await?, + Commands::ServerInfo => try_serverinfo().await?, + Commands::ServerConfig => try_serverconfig().await?, + Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?, + Commands::AuthServer => crate::auth::server::serve().await?, + Commands::ServerStatus => try_serverstatus().await?, + Commands::TunnelConfig => try_tun_config().await?, + Commands::NetworkAdd(args) => { + try_network_add(args.id, args.network_type, &args.payload_path).await? + } + Commands::NetworkList => try_network_list().await?, + Commands::NetworkReorder(args) => try_network_reorder(args.id, args.index).await?, + Commands::NetworkDelete(args) => try_network_delete(args.id).await?, + } Ok(()) } -#[tokio::main(flavor = "current_thread")] -async fn main() { - lol().await.unwrap(); +#[cfg(not(any(target_os = "linux", target_vendor = "apple")))] +pub fn main() { + eprintln!("This platform is not supported") } diff --git a/burrow/src/tracing.rs b/burrow/src/tracing.rs new file mode 100644 index 0000000..861b41f --- /dev/null +++ b/burrow/src/tracing.rs @@ -0,0 +1,64 @@ +use std::sync::Once; + +use tracing::{error, info}; +use tracing_subscriber::{ + layer::{Layer, SubscriberExt}, + EnvFilter, + Registry, +}; + +static TRACING: Once = Once::new(); + +pub fn initialize() { + TRACING.call_once(|| { + if let Err(e) = tracing_log::LogTracer::init() { + error!("Failed to initialize LogTracer: {}", e); + } + + #[cfg(target_os = "windows")] + let system_log = Some(tracing_subscriber::fmt::layer()); + + #[cfg(target_os = "linux")] + let system_log = match tracing_journald::layer() { + Ok(layer) => Some(layer), + Err(e) => { + if e.kind() != std::io::ErrorKind::NotFound { + error!("Failed to initialize journald: {}", e); + } + None + } + }; + + #[cfg(target_vendor = "apple")] + let system_log = Some(tracing_oslog::OsLogger::new( + "com.hackclub.burrow", + "tracing", + )); + + let stderr = (console::user_attended_stderr() || system_log.is_none()).then(|| { + tracing_subscriber::fmt::layer() + .with_level(true) + .with_writer(std::io::stderr) + .with_line_number(true) + .compact() + .with_filter(EnvFilter::from_default_env()) + }); + + let subscriber = Registry::default().with(stderr).with(system_log); + + #[cfg(feature = "tokio-console")] + let subscriber = subscriber.with( + console_subscriber::spawn().with_filter( + EnvFilter::from_default_env() + .add_directive("tokio=trace".parse().unwrap()) + .add_directive("runtime=trace".parse().unwrap()), + ), + ); + + if let Err(e) = tracing::subscriber::set_global_default(subscriber) { + error!("Failed to initialize logging: {}", e); + } + + info!("Initialized logging") + }); +} diff --git a/burrow/src/wireguard/config.rs b/burrow/src/wireguard/config.rs new file mode 100644 index 0000000..5766675 --- /dev/null +++ b/burrow/src/wireguard/config.rs @@ -0,0 +1,199 @@ +use std::{net::ToSocketAddrs, str::FromStr}; + +use anyhow::{anyhow, Error, Result}; +use base64::{engine::general_purpose, Engine}; +use fehler::throws; +use ini::{Ini, Properties}; +use ip_network::IpNetwork; +use serde::{Deserialize, Serialize}; +use x25519_dalek::{PublicKey, StaticSecret}; + +use super::inifield::IniField; +use crate::wireguard::{Interface as WgInterface, Peer as WgPeer}; + +#[throws] +fn parse_key(string: &str) -> [u8; 32] { + let value = general_purpose::STANDARD.decode(string)?; + let mut key = [0u8; 32]; + key.copy_from_slice(&value[..]); + key +} + +#[throws] +fn parse_secret_key(string: &str) -> StaticSecret { + let key = parse_key(string)?; + StaticSecret::from(key) +} + +#[throws] +fn parse_public_key(string: &str) -> PublicKey { + let key = parse_key(string)?; + PublicKey::from(key) +} + +/// A raw version of Peer Config that can be used later to reflect configuration files. +/// This should be later converted to a `WgPeer`. +/// Refers to https://github.com/pirate/wireguard-docs?tab=readme-ov-file#overview +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Peer { + pub public_key: String, + pub preshared_key: Option, + pub allowed_ips: Vec, + pub endpoint: String, + pub persistent_keepalive: Option, + pub name: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Interface { + pub private_key: String, + pub address: Vec, + pub listen_port: Option, + pub dns: Vec, + pub mtu: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Config { + #[serde(rename = "Peer")] + pub peers: Vec, + pub interface: Interface, // Support for multiple interfaces? +} + +impl TryFrom for WgInterface { + type Error = anyhow::Error; + + fn try_from(cfig: Config) -> Result { + let sk = parse_secret_key(&cfig.interface.private_key)?; + let wg_peers: Vec = cfig + .peers + .iter() + .map(|p| { + Ok(WgPeer { + private_key: sk.clone(), + public_key: parse_public_key(&p.public_key)?, + endpoint: p + .endpoint + .to_socket_addrs()? + .find(|sock| sock.is_ipv4()) + .ok_or(anyhow!("DNS Lookup Fails!"))?, + preshared_key: match &p.preshared_key { + None => Ok(None), + Some(k) => parse_key(k).map(Some), + }?, + allowed_ips: p + .allowed_ips + .iter() + .map(|ip_addr| { + IpNetwork::from_str(ip_addr) + .map_err(|e| anyhow!("Error parsing IP Network {}: {}", ip_addr, e)) + }) + .collect::>>()?, + }) + }) + .collect::>>()?; + WgInterface::new(wg_peers) + } +} + +impl Default for Config { + fn default() -> Self { + Self { + interface: Interface { + private_key: "OEPVdomeLTxTIBvv3TYsJRge0Hp9NMiY0sIrhT8OWG8=".into(), + address: vec!["10.13.13.2/24".into()], + listen_port: Some(51820), + dns: Default::default(), + mtu: Default::default(), + }, + peers: vec![Peer { + endpoint: "wg.burrow.rs:51820".into(), + allowed_ips: vec!["8.8.8.8/32".into(), "0.0.0.0/0".into()], + public_key: "8GaFjVO6c4luCHG4ONO+1bFG8tO+Zz5/Gy+Geht1USM=".into(), + preshared_key: Some("ha7j4BjD49sIzyF9SNlbueK0AMHghlj6+u0G3bzC698=".into()), + persistent_keepalive: Default::default(), + name: Default::default(), + }], + } + } +} + +fn props_get(props: &Properties, key: &str) -> Result +where + T: TryFrom, +{ + IniField::try_from(props.get(key))?.try_into() +} + +impl TryFrom<&Properties> for Interface { + type Error = anyhow::Error; + + fn try_from(props: &Properties) -> Result { + Ok(Self { + private_key: props_get(props, "PrivateKey")?, + address: props_get(props, "Address")?, + listen_port: props_get(props, "ListenPort")?, + dns: props_get(props, "DNS")?, + mtu: props_get(props, "MTU")?, + }) + } +} + +impl TryFrom<&Properties> for Peer { + type Error = anyhow::Error; + + fn try_from(props: &Properties) -> Result { + Ok(Self { + public_key: props_get(props, "PublicKey")?, + preshared_key: props_get(props, "PresharedKey")?, + allowed_ips: props_get(props, "AllowedIPs")?, + endpoint: props_get(props, "Endpoint")?, + persistent_keepalive: props_get(props, "PersistentKeepalive")?, + name: props_get(props, "Name")?, + }) + } +} + +impl Config { + pub fn from_toml(toml: &str) -> Result { + toml::from_str(toml).map_err(Into::into) + } + + pub fn from_ini(ini: &str) -> Result { + let ini = Ini::load_from_str(ini)?; + let interface = ini + .section(Some("Interface")) + .ok_or(anyhow!("Interface section not found"))?; + let peers = ini.section_all(Some("Peer")); + Ok(Self { + interface: Interface::try_from(interface)?, + peers: peers + .into_iter() + .map(|v| Peer::try_from(v)) + .collect::>>()?, + }) + } + + pub fn from_content_fmt(content: &str, fmt: &str) -> Result { + match fmt { + "toml" => Self::from_toml(content), + "ini" | "conf" => Self::from_ini(content), + _ => Err(anyhow::anyhow!("Unsupported format: {}", fmt)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tst_config_toml() { + let cfig = Config::default(); + let toml = toml::to_string(&cfig).unwrap(); + println!("{}", &toml); + insta::assert_snapshot!(toml); + let cfig2: Config = toml::from_str(&toml).unwrap(); + assert_eq!(cfig, cfig2); + } +} diff --git a/burrow/src/wireguard/iface.rs b/burrow/src/wireguard/iface.rs new file mode 100755 index 0000000..321801b --- /dev/null +++ b/burrow/src/wireguard/iface.rs @@ -0,0 +1,232 @@ +use std::{net::IpAddr, ops::Deref, sync::Arc}; + +use anyhow::Error; +use fehler::throws; +use futures::future::join_all; +use ip_network_table::IpNetworkTable; +use tokio::sync::{Notify, RwLock}; +use tracing::{debug, error}; +use tun::tokio::TunInterface; + +use super::{noise::Tunnel, Peer, PeerPcb}; + +pub struct IndexedPcbs { + pcbs: Vec>, + allowed_ips: IpNetworkTable, +} + +impl Default for IndexedPcbs { + fn default() -> Self { + Self::new() + } +} + +impl IndexedPcbs { + pub fn new() -> Self { + Self { + pcbs: vec![], + allowed_ips: IpNetworkTable::new(), + } + } + + pub fn insert(&mut self, pcb: PeerPcb) { + let idx: usize = self.pcbs.len(); + for allowed_ip in pcb.allowed_ips.iter() { + self.allowed_ips.insert(*allowed_ip, idx); + } + self.pcbs.insert(idx, Arc::new(pcb)); + } + + pub fn find(&self, addr: IpAddr) -> Option { + let (_, &idx) = self.allowed_ips.longest_match(addr)?; + Some(idx) + } +} + +impl FromIterator for IndexedPcbs { + fn from_iter>(iter: I) -> Self { + iter.into_iter().fold(Self::new(), |mut acc, pcb| { + acc.insert(pcb); + acc + }) + } +} + +enum IfaceStatus { + Running, + Idle, +} + +pub struct Interface { + pub tun: Arc>>, + pub pcbs: Arc, + status: Arc>, + stop_notifier: Arc, +} + +async fn is_running(status: Arc>) -> bool { + let st = status.read().await; + matches!(st.deref(), IfaceStatus::Running) +} + +impl Interface { + #[throws] + pub fn new>(peers: I) -> Self { + let pcbs: IndexedPcbs = peers + .into_iter() + .map(PeerPcb::new) + .collect::>()?; + + let pcbs = Arc::new(pcbs); + Self { + pcbs, + tun: Arc::new(RwLock::new(None)), + status: Arc::new(RwLock::new(IfaceStatus::Idle)), + stop_notifier: Arc::new(Notify::new()), + } + } + + pub async fn set_tun(&self, tun: TunInterface) { + debug!("Setting tun interface"); + self.tun.write().await.replace(tun); + let mut st = self.status.write().await; + *st = IfaceStatus::Running; + } + + pub async fn set_tun_ref(&mut self, tun: Arc>>) { + self.tun = tun; + let mut st = self.status.write().await; + *st = IfaceStatus::Running; + } + + pub fn get_tun(&self) -> Arc>> { + self.tun.clone() + } + + pub async fn remove_tun(&self) { + let mut st = self.status.write().await; + self.stop_notifier.notify_waiters(); + *st = IfaceStatus::Idle; + } + + pub async fn run(&self) -> anyhow::Result<()> { + let pcbs = self.pcbs.clone(); + let tun = self.tun.clone(); + let status = self.status.clone(); + let stop_notifier = self.stop_notifier.clone(); + log::info!("Starting interface"); + + let outgoing = async move { + while is_running(status.clone()).await { + let mut buf = [0u8; 3000]; + + let src = { + let t = tun.read().await; + let Some(_tun) = t.as_ref() else { + continue; + }; + tokio::select! { + _ = stop_notifier.notified() => continue, + pkg = _tun.recv(&mut buf[..]) => match pkg { + Ok(len) => &buf[..len], + Err(e) => { + error!("Failed to read from interface: {}", e); + continue + } + }, + } + }; + + let dst_addr = match Tunnel::dst_address(src) { + Some(addr) => addr, + None => { + debug!("No destination found"); + continue; + } + }; + + debug!("Routing packet to {}", dst_addr); + + let Some(idx) = pcbs.find(dst_addr) else { + continue + }; + + debug!("Found peer:{}", idx); + + match pcbs.pcbs[idx].send(src).await { + Ok(..) => { + let addr = pcbs.pcbs[idx].endpoint; + debug!("Sent packet to peer {}", addr); + } + Err(e) => { + log::error!("Failed to send packet {}", e); + continue; + } + }; + } + }; + + let mut tsks = vec![]; + let tun = self.tun.clone(); + let outgoing = tokio::task::spawn(outgoing); + tsks.push(outgoing); + debug!("preparing to spawn read tasks"); + + { + let pcbs = &self.pcbs; + for i in 0..pcbs.pcbs.len() { + debug!("spawning read task for peer {}", i); + let pcb = pcbs.pcbs[i].clone(); + let tun = tun.clone(); + let main_tsk = async move { + if let Err(e) = pcb.open_if_closed().await { + log::error!("failed to open pcb: {}", e); + return; + } + let r2 = pcb.run(tun).await; + if let Err(e) = r2 { + log::error!("failed to run pcb: {}", e); + } else { + debug!("pcb ran successfully"); + } + }; + + let pcb = pcbs.pcbs[i].clone(); + let status = self.status.clone(); + let update_timers_tsk = async move { + let mut buf = [0u8; 65535]; + while is_running(status.clone()).await { + tokio::time::sleep(tokio::time::Duration::from_millis(250)).await; + match pcb.update_timers(&mut buf).await { + Ok(..) => (), + Err(e) => { + error!("Failed to update timers: {}", e); + return; + } + } + } + }; + + let pcb = pcbs.pcbs[i].clone(); + let status = self.status.clone(); + let reset_rate_limiter_tsk = async move { + while is_running(status.clone()).await { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + pcb.reset_rate_limiter().await; + } + }; + tsks.extend(vec![ + tokio::spawn(main_tsk), + tokio::spawn(update_timers_tsk), + tokio::spawn(reset_rate_limiter_tsk), + ]); + debug!("task made.."); + } + debug!("spawned read tasks"); + } + debug!("preparing to join.."); + join_all(tsks).await; + debug!("joined!"); + Ok(()) + } +} diff --git a/burrow/src/wireguard/inifield.rs b/burrow/src/wireguard/inifield.rs new file mode 100644 index 0000000..946868d --- /dev/null +++ b/burrow/src/wireguard/inifield.rs @@ -0,0 +1,81 @@ +use std::str::FromStr; + +use anyhow::{Error, Result}; + +pub struct IniField(String); + +impl FromStr for IniField { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Self(s.to_string())) + } +} + +impl TryFrom for Vec { + type Error = Error; + + fn try_from(field: IniField) -> Result { + Ok(field.0.split(',').map(|s| s.trim().to_string()).collect()) + } +} + +impl TryFrom for u32 { + type Error = Error; + + fn try_from(value: IniField) -> Result { + value.0.parse().map_err(Error::from) + } +} + +impl TryFrom for Option { + type Error = Error; + + fn try_from(value: IniField) -> Result { + if value.0.is_empty() { + Ok(None) + } else { + value.0.parse().map(Some).map_err(Error::from) + } + } +} + +impl TryFrom for String { + type Error = Error; + + fn try_from(value: IniField) -> Result { + Ok(value.0) + } +} + +impl TryFrom for Option { + type Error = Error; + + fn try_from(value: IniField) -> Result { + if value.0.is_empty() { + Ok(None) + } else { + Ok(Some(value.0)) + } + } +} + +impl TryFrom> for IniField +where + T: ToString, +{ + type Error = Error; + + fn try_from(value: Option) -> Result { + Ok(match value { + Some(v) => Self(v.to_string()), + None => Self(String::new()), + }) + } +} + +impl IniField { + fn new(value: &str) -> Self { + Self(value.to_string()) + } +} diff --git a/burrow/src/wireguard/mod.rs b/burrow/src/wireguard/mod.rs new file mode 100755 index 0000000..cfb4585 --- /dev/null +++ b/burrow/src/wireguard/mod.rs @@ -0,0 +1,11 @@ +pub mod config; +mod iface; +mod inifield; +mod noise; +mod pcb; +mod peer; + +pub use config::Config; +pub use iface::Interface; +pub use pcb::PeerPcb; +pub use peer::Peer; diff --git a/burrow/src/wireguard/noise/errors.rs b/burrow/src/wireguard/noise/errors.rs new file mode 100755 index 0000000..e196635 --- /dev/null +++ b/burrow/src/wireguard/noise/errors.rs @@ -0,0 +1,20 @@ +// Copyright (c) 2019 Cloudflare, Inc. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause + +#[derive(Debug)] +pub enum WireGuardError { + DestinationBufferTooSmall, + UnexpectedPacket, + WrongIndex, + WrongKey, + InvalidTai64nTimestamp, + WrongTai64nTimestamp, + InvalidMac, + InvalidAeadTag, + InvalidCounter, + DuplicateCounter, + InvalidPacket, + NoCurrentSession, + ConnectionExpired, + UnderLoad, +} diff --git a/burrow/src/wireguard/noise/handshake.rs b/burrow/src/wireguard/noise/handshake.rs new file mode 100755 index 0000000..2ec0c6a --- /dev/null +++ b/burrow/src/wireguard/noise/handshake.rs @@ -0,0 +1,900 @@ +// Copyright (c) 2019 Cloudflare, Inc. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause + +use std::{ + convert::TryInto, + time::{Duration, Instant, SystemTime}, +}; + +use aead::{Aead, Payload}; +use blake2::{ + digest::{FixedOutput, KeyInit}, + Blake2s256, + Blake2sMac, + Digest, +}; +use chacha20poly1305::XChaCha20Poly1305; +use rand_core::OsRng; +use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305}; + +use super::{ + errors::WireGuardError, + session::Session, + x25519, + HandshakeInit, + HandshakeResponse, + PacketCookieReply, +}; + +pub(crate) const LABEL_MAC1: &[u8; 8] = b"mac1----"; +pub(crate) const LABEL_COOKIE: &[u8; 8] = b"cookie--"; +const KEY_LEN: usize = 32; +const TIMESTAMP_LEN: usize = 12; + +// initiator.chaining_key = HASH(CONSTRUCTION) +const INITIAL_CHAIN_KEY: [u8; KEY_LEN] = [ + 96, 226, 109, 174, 243, 39, 239, 192, 46, 195, 53, 226, 160, 37, 210, 208, 22, 235, 66, 6, 248, + 114, 119, 245, 45, 56, 209, 152, 139, 120, 205, 54, +]; + +// initiator.chaining_hash = HASH(initiator.chaining_key || IDENTIFIER) +const INITIAL_CHAIN_HASH: [u8; KEY_LEN] = [ + 34, 17, 179, 97, 8, 26, 197, 102, 105, 18, 67, 219, 69, 138, 213, 50, 45, 156, 108, 102, 34, + 147, 232, 183, 14, 225, 156, 101, 186, 7, 158, 243, +]; + +#[inline] +pub(crate) fn b2s_hash(data1: &[u8], data2: &[u8]) -> [u8; 32] { + let mut hash = Blake2s256::new(); + hash.update(data1); + hash.update(data2); + hash.finalize().into() +} + +#[inline] +/// RFC 2401 HMAC+Blake2s, not to be confused with *keyed* Blake2s +pub(crate) fn b2s_hmac(key: &[u8], data1: &[u8]) -> [u8; 32] { + use blake2::digest::Update; + type HmacBlake2s = hmac::SimpleHmac; + let mut hmac = HmacBlake2s::new_from_slice(key).unwrap(); + hmac.update(data1); + hmac.finalize_fixed().into() +} + +#[inline] +/// Like b2s_hmac, but chain data1 and data2 together +pub(crate) fn b2s_hmac2(key: &[u8], data1: &[u8], data2: &[u8]) -> [u8; 32] { + use blake2::digest::Update; + type HmacBlake2s = hmac::SimpleHmac; + let mut hmac = HmacBlake2s::new_from_slice(key).unwrap(); + hmac.update(data1); + hmac.update(data2); + hmac.finalize_fixed().into() +} + +#[inline] +pub(crate) fn b2s_keyed_mac_16(key: &[u8], data1: &[u8]) -> [u8; 16] { + let mut hmac = Blake2sMac::new_from_slice(key).unwrap(); + blake2::digest::Update::update(&mut hmac, data1); + hmac.finalize_fixed().into() +} + +#[inline] +pub(crate) fn b2s_keyed_mac_16_2(key: &[u8], data1: &[u8], data2: &[u8]) -> [u8; 16] { + let mut hmac = Blake2sMac::new_from_slice(key).unwrap(); + blake2::digest::Update::update(&mut hmac, data1); + blake2::digest::Update::update(&mut hmac, data2); + hmac.finalize_fixed().into() +} + +pub(crate) fn b2s_mac_24(key: &[u8], data1: &[u8]) -> [u8; 24] { + let mut hmac = Blake2sMac::new_from_slice(key).unwrap(); + blake2::digest::Update::update(&mut hmac, data1); + hmac.finalize_fixed().into() +} + +#[inline] +/// This wrapper involves an extra copy and MAY BE SLOWER +fn aead_chacha20_seal(ciphertext: &mut [u8], key: &[u8], counter: u64, data: &[u8], aad: &[u8]) { + let mut nonce: [u8; 12] = [0; 12]; + nonce[4..12].copy_from_slice(&counter.to_le_bytes()); + + aead_chacha20_seal_inner(ciphertext, key, nonce, data, aad) +} + +#[inline] +fn aead_chacha20_seal_inner( + ciphertext: &mut [u8], + key: &[u8], + nonce: [u8; 12], + data: &[u8], + aad: &[u8], +) { + let key = LessSafeKey::new(UnboundKey::new(&CHACHA20_POLY1305, key).unwrap()); + + ciphertext[..data.len()].copy_from_slice(data); + + let tag = key + .seal_in_place_separate_tag( + Nonce::assume_unique_for_key(nonce), + Aad::from(aad), + &mut ciphertext[..data.len()], + ) + .unwrap(); + + ciphertext[data.len()..].copy_from_slice(tag.as_ref()); +} + +#[inline] +/// This wrapper involves an extra copy and MAY BE SLOWER +fn aead_chacha20_open( + buffer: &mut [u8], + key: &[u8], + counter: u64, + data: &[u8], + aad: &[u8], +) -> Result<(), WireGuardError> { + let mut nonce: [u8; 12] = [0; 12]; + nonce[4..].copy_from_slice(&counter.to_le_bytes()); + aead_chacha20_open_inner(buffer, key, nonce, data, aad) + .map_err(|_| WireGuardError::InvalidAeadTag)?; + Ok(()) +} + +#[inline] +fn aead_chacha20_open_inner( + buffer: &mut [u8], + key: &[u8], + nonce: [u8; 12], + data: &[u8], + aad: &[u8], +) -> Result<(), ring::error::Unspecified> { + let key = LessSafeKey::new(UnboundKey::new(&CHACHA20_POLY1305, key).unwrap()); + + let mut inner_buffer = data.to_owned(); + + let plaintext = key.open_in_place( + Nonce::assume_unique_for_key(nonce), + Aad::from(aad), + &mut inner_buffer, + )?; + + buffer.copy_from_slice(plaintext); + + Ok(()) +} + +#[derive(Debug)] +/// This struct represents a 12 byte [Tai64N](https://cr.yp.to/libtai/tai64.html) timestamp +struct Tai64N { + secs: u64, + nano: u32, +} + +#[derive(Debug)] +/// This struct computes a [Tai64N](https://cr.yp.to/libtai/tai64.html) timestamp from current system time +struct TimeStamper { + duration_at_start: Duration, + instant_at_start: Instant, +} + +impl TimeStamper { + /// Create a new TimeStamper + pub fn new() -> TimeStamper { + TimeStamper { + duration_at_start: SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap(), + instant_at_start: Instant::now(), + } + } + + /// Take time reading and generate a 12 byte timestamp + pub fn stamp(&self) -> [u8; 12] { + const TAI64_BASE: u64 = (1u64 << 62) + 37; + let mut ext_stamp = [0u8; 12]; + let stamp = Instant::now().duration_since(self.instant_at_start) + self.duration_at_start; + ext_stamp[0..8].copy_from_slice(&(stamp.as_secs() + TAI64_BASE).to_be_bytes()); + ext_stamp[8..12].copy_from_slice(&stamp.subsec_nanos().to_be_bytes()); + ext_stamp + } +} + +impl Tai64N { + /// A zeroed out timestamp + fn zero() -> Tai64N { + Tai64N { secs: 0, nano: 0 } + } + + /// Parse a timestamp from a 12 byte u8 slice + fn parse(buf: &[u8; 12]) -> Result { + if buf.len() < 12 { + return Err(WireGuardError::InvalidTai64nTimestamp) + } + + let (sec_bytes, nano_bytes) = buf.split_at(std::mem::size_of::()); + let secs = u64::from_be_bytes(sec_bytes.try_into().unwrap()); + let nano = u32::from_be_bytes(nano_bytes.try_into().unwrap()); + + // WireGuard does not actually expect tai64n timestamp, just monotonically + // increasing one if secs < (1u64 << 62) || secs >= (1u64 << 63) { + // return Err(WireGuardError::InvalidTai64nTimestamp); + //}; + // if nano >= 1_000_000_000 { + // return Err(WireGuardError::InvalidTai64nTimestamp); + //} + + Ok(Tai64N { secs, nano }) + } + + /// Check if this timestamp represents a time that is chronologically after + /// the time represented by the other timestamp + pub fn after(&self, other: &Tai64N) -> bool { + (self.secs > other.secs) || ((self.secs == other.secs) && (self.nano > other.nano)) + } +} + +/// Parameters used by the noise protocol +struct NoiseParams { + /// Our static public key + static_public: x25519::PublicKey, + /// Our static private key + static_private: x25519::StaticSecret, + /// Static public key of the other party + peer_static_public: x25519::PublicKey, + /// A shared key = DH(static_private, peer_static_public) + static_shared: x25519::SharedSecret, + /// A pre-computation of HASH("mac1----", peer_static_public) for this peer + sending_mac1_key: [u8; KEY_LEN], + /// An optional preshared key + preshared_key: Option<[u8; KEY_LEN]>, +} + +impl std::fmt::Debug for NoiseParams { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NoiseParams") + .field("static_public", &self.static_public) + .field("static_private", &"") + .field("peer_static_public", &self.peer_static_public) + .field("static_shared", &"") + .field("sending_mac1_key", &self.sending_mac1_key) + .field("preshared_key", &self.preshared_key) + .finish() + } +} + +struct HandshakeInitSentState { + local_index: u32, + hash: [u8; KEY_LEN], + chaining_key: [u8; KEY_LEN], + ephemeral_private: x25519::ReusableSecret, + time_sent: Instant, +} + +impl std::fmt::Debug for HandshakeInitSentState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HandshakeInitSentState") + .field("local_index", &self.local_index) + .field("hash", &self.hash) + .field("chaining_key", &self.chaining_key) + .field("ephemeral_private", &"") + .field("time_sent", &self.time_sent) + .finish() + } +} + +#[derive(Debug)] +enum HandshakeState { + /// No handshake in process + None, + /// We initiated the handshake + InitSent(HandshakeInitSentState), + /// Handshake initiated by peer + InitReceived { + hash: [u8; KEY_LEN], + chaining_key: [u8; KEY_LEN], + peer_ephemeral_public: x25519::PublicKey, + peer_index: u32, + }, + /// Handshake was established too long ago (implies no handshake is in + /// progress) + Expired, +} + +#[derive(Debug)] +pub struct Handshake { + params: NoiseParams, + /// Index of the next session + next_index: u32, + /// Allow to have two outgoing handshakes in flight, because sometimes we + /// may receive a delayed response to a handshake with bad networks + previous: HandshakeState, + /// Current handshake state + state: HandshakeState, + cookies: Cookies, + /// The timestamp of the last handshake we received + last_handshake_timestamp: Tai64N, + // TODO: make TimeStamper a singleton + stamper: TimeStamper, + pub(super) last_rtt: Option, +} + +#[derive(Default, Debug)] +struct Cookies { + last_mac1: Option<[u8; 16]>, + index: u32, + write_cookie: Option<[u8; 16]>, +} + +#[derive(Debug)] +pub struct HalfHandshake { + pub peer_index: u32, + pub peer_static_public: [u8; 32], +} + +pub fn parse_handshake_anon( + static_private: &x25519::StaticSecret, + static_public: &x25519::PublicKey, + packet: &HandshakeInit, +) -> Result { + let peer_index = packet.sender_idx; + // initiator.chaining_key = HASH(CONSTRUCTION) + let mut chaining_key = INITIAL_CHAIN_KEY; + // initiator.hash = HASH(HASH(initiator.chaining_key || IDENTIFIER) || + // responder.static_public) + let mut hash = INITIAL_CHAIN_HASH; + hash = b2s_hash(&hash, static_public.as_bytes()); + // msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private) + let peer_ephemeral_public = x25519::PublicKey::from(*packet.unencrypted_ephemeral); + // initiator.hash = HASH(initiator.hash || msg.unencrypted_ephemeral) + hash = b2s_hash(&hash, peer_ephemeral_public.as_bytes()); + // temp = HMAC(initiator.chaining_key, msg.unencrypted_ephemeral) + // initiator.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac( + &b2s_hmac(&chaining_key, peer_ephemeral_public.as_bytes()), + &[0x01], + ); + // temp = HMAC(initiator.chaining_key, DH(initiator.ephemeral_private, + // responder.static_public)) + let ephemeral_shared = static_private.diffie_hellman(&peer_ephemeral_public); + let temp = b2s_hmac(&chaining_key, &ephemeral_shared.to_bytes()); + // initiator.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // key = HMAC(temp, initiator.chaining_key || 0x2) + let key = b2s_hmac2(&temp, &chaining_key, &[0x02]); + + let mut peer_static_public = [0u8; KEY_LEN]; + // msg.encrypted_static = AEAD(key, 0, initiator.static_public, initiator.hash) + aead_chacha20_open( + &mut peer_static_public, + &key, + 0, + packet.encrypted_static, + &hash, + )?; + + Ok(HalfHandshake { peer_index, peer_static_public }) +} + +impl NoiseParams { + /// New noise params struct from our secret key, peers public key, and + /// optional preshared key + fn new( + static_private: x25519::StaticSecret, + static_public: x25519::PublicKey, + peer_static_public: x25519::PublicKey, + preshared_key: Option<[u8; 32]>, + ) -> Result { + let static_shared = static_private.diffie_hellman(&peer_static_public); + + let initial_sending_mac_key = b2s_hash(LABEL_MAC1, peer_static_public.as_bytes()); + + Ok(NoiseParams { + static_public, + static_private, + peer_static_public, + static_shared, + sending_mac1_key: initial_sending_mac_key, + preshared_key, + }) + } + + /// Set a new private key + fn set_static_private( + &mut self, + static_private: x25519::StaticSecret, + static_public: x25519::PublicKey, + ) -> Result<(), WireGuardError> { + // Check that the public key indeed matches the private key + let check_key = x25519::PublicKey::from(&static_private); + assert_eq!(check_key.as_bytes(), static_public.as_bytes()); + + self.static_private = static_private; + self.static_public = static_public; + + self.static_shared = self.static_private.diffie_hellman(&self.peer_static_public); + Ok(()) + } +} + +impl Handshake { + pub(crate) fn new( + static_private: x25519::StaticSecret, + static_public: x25519::PublicKey, + peer_static_public: x25519::PublicKey, + global_idx: u32, + preshared_key: Option<[u8; 32]>, + ) -> Result { + let params = NoiseParams::new( + static_private, + static_public, + peer_static_public, + preshared_key, + )?; + + Ok(Handshake { + params, + next_index: global_idx, + previous: HandshakeState::None, + state: HandshakeState::None, + last_handshake_timestamp: Tai64N::zero(), + stamper: TimeStamper::new(), + cookies: Default::default(), + last_rtt: None, + }) + } + + pub(crate) fn is_in_progress(&self) -> bool { + !matches!(self.state, HandshakeState::None | HandshakeState::Expired) + } + + pub(crate) fn timer(&self) -> Option { + match self.state { + HandshakeState::InitSent(HandshakeInitSentState { time_sent, .. }) => Some(time_sent), + _ => None, + } + } + + pub(crate) fn set_expired(&mut self) { + self.previous = HandshakeState::Expired; + self.state = HandshakeState::Expired; + } + + pub(crate) fn is_expired(&self) -> bool { + matches!(self.state, HandshakeState::Expired) + } + + pub(crate) fn has_cookie(&self) -> bool { + self.cookies.write_cookie.is_some() + } + + pub(crate) fn clear_cookie(&mut self) { + self.cookies.write_cookie = None; + } + + // The index used is 24 bits for peer index, allowing for 16M active peers per + // server and 8 bits for cyclic session index + fn inc_index(&mut self) -> u32 { + let index = self.next_index; + let idx8 = index as u8; + self.next_index = (index & !0xff) | u32::from(idx8.wrapping_add(1)); + self.next_index + } + + pub(crate) fn set_static_private( + &mut self, + private_key: x25519::StaticSecret, + public_key: x25519::PublicKey, + ) -> Result<(), WireGuardError> { + self.params.set_static_private(private_key, public_key) + } + + pub(super) fn receive_handshake_initialization<'a>( + &mut self, + packet: HandshakeInit, + dst: &'a mut [u8], + ) -> Result<(&'a mut [u8], Session), WireGuardError> { + // initiator.chaining_key = HASH(CONSTRUCTION) + let mut chaining_key = INITIAL_CHAIN_KEY; + // initiator.hash = HASH(HASH(initiator.chaining_key || IDENTIFIER) || + // responder.static_public) + let mut hash = INITIAL_CHAIN_HASH; + hash = b2s_hash(&hash, self.params.static_public.as_bytes()); + // msg.sender_index = little_endian(initiator.sender_index) + let peer_index = packet.sender_idx; + // msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private) + let peer_ephemeral_public = x25519::PublicKey::from(*packet.unencrypted_ephemeral); + // initiator.hash = HASH(initiator.hash || msg.unencrypted_ephemeral) + hash = b2s_hash(&hash, peer_ephemeral_public.as_bytes()); + // temp = HMAC(initiator.chaining_key, msg.unencrypted_ephemeral) + // initiator.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac( + &b2s_hmac(&chaining_key, peer_ephemeral_public.as_bytes()), + &[0x01], + ); + // temp = HMAC(initiator.chaining_key, DH(initiator.ephemeral_private, + // responder.static_public)) + let ephemeral_shared = self + .params + .static_private + .diffie_hellman(&peer_ephemeral_public); + let temp = b2s_hmac(&chaining_key, &ephemeral_shared.to_bytes()); + // initiator.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // key = HMAC(temp, initiator.chaining_key || 0x2) + let key = b2s_hmac2(&temp, &chaining_key, &[0x02]); + + let mut peer_static_public_decrypted = [0u8; KEY_LEN]; + // msg.encrypted_static = AEAD(key, 0, initiator.static_public, initiator.hash) + aead_chacha20_open( + &mut peer_static_public_decrypted, + &key, + 0, + packet.encrypted_static, + &hash, + )?; + + ring::constant_time::verify_slices_are_equal( + self.params.peer_static_public.as_bytes(), + &peer_static_public_decrypted, + ) + .map_err(|_| WireGuardError::WrongKey)?; + + // initiator.hash = HASH(initiator.hash || msg.encrypted_static) + hash = b2s_hash(&hash, packet.encrypted_static); + // temp = HMAC(initiator.chaining_key, DH(initiator.static_private, + // responder.static_public)) + let temp = b2s_hmac(&chaining_key, self.params.static_shared.as_bytes()); + // initiator.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // key = HMAC(temp, initiator.chaining_key || 0x2) + let key = b2s_hmac2(&temp, &chaining_key, &[0x02]); + // msg.encrypted_timestamp = AEAD(key, 0, TAI64N(), initiator.hash) + let mut timestamp = [0u8; TIMESTAMP_LEN]; + aead_chacha20_open(&mut timestamp, &key, 0, packet.encrypted_timestamp, &hash)?; + + let timestamp = Tai64N::parse(×tamp)?; + if !timestamp.after(&self.last_handshake_timestamp) { + // Possibly a replay + return Err(WireGuardError::WrongTai64nTimestamp) + } + self.last_handshake_timestamp = timestamp; + + // initiator.hash = HASH(initiator.hash || msg.encrypted_timestamp) + hash = b2s_hash(&hash, packet.encrypted_timestamp); + + self.previous = std::mem::replace(&mut self.state, HandshakeState::InitReceived { + chaining_key, + hash, + peer_ephemeral_public, + peer_index, + }); + + self.format_handshake_response(dst) + } + + pub(super) fn receive_handshake_response( + &mut self, + packet: HandshakeResponse, + ) -> Result { + // Check if there is a handshake awaiting a response and return the correct one + let (state, is_previous) = match (&self.state, &self.previous) { + (HandshakeState::InitSent(s), _) if s.local_index == packet.receiver_idx => (s, false), + (_, HandshakeState::InitSent(s)) if s.local_index == packet.receiver_idx => (s, true), + _ => return Err(WireGuardError::UnexpectedPacket), + }; + + let peer_index = packet.sender_idx; + let local_index = state.local_index; + + let unencrypted_ephemeral = x25519::PublicKey::from(*packet.unencrypted_ephemeral); + // msg.unencrypted_ephemeral = DH_PUBKEY(responder.ephemeral_private) + // responder.hash = HASH(responder.hash || msg.unencrypted_ephemeral) + let mut hash = b2s_hash(&state.hash, unencrypted_ephemeral.as_bytes()); + // temp = HMAC(responder.chaining_key, msg.unencrypted_ephemeral) + let temp = b2s_hmac(&state.chaining_key, unencrypted_ephemeral.as_bytes()); + // responder.chaining_key = HMAC(temp, 0x1) + let mut chaining_key = b2s_hmac(&temp, &[0x01]); + // temp = HMAC(responder.chaining_key, DH(responder.ephemeral_private, + // initiator.ephemeral_public)) + let ephemeral_shared = state + .ephemeral_private + .diffie_hellman(&unencrypted_ephemeral); + let temp = b2s_hmac(&chaining_key, &ephemeral_shared.to_bytes()); + // responder.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // temp = HMAC(responder.chaining_key, DH(responder.ephemeral_private, + // initiator.static_public)) + let temp = b2s_hmac( + &chaining_key, + &self + .params + .static_private + .diffie_hellman(&unencrypted_ephemeral) + .to_bytes(), + ); + // responder.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // temp = HMAC(responder.chaining_key, preshared_key) + let temp = b2s_hmac( + &chaining_key, + &self.params.preshared_key.unwrap_or([0u8; 32])[..], + ); + // responder.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // temp2 = HMAC(temp, responder.chaining_key || 0x2) + let temp2 = b2s_hmac2(&temp, &chaining_key, &[0x02]); + // key = HMAC(temp, temp2 || 0x3) + let key = b2s_hmac2(&temp, &temp2, &[0x03]); + // responder.hash = HASH(responder.hash || temp2) + hash = b2s_hash(&hash, &temp2); + // msg.encrypted_nothing = AEAD(key, 0, [empty], responder.hash) + aead_chacha20_open(&mut [], &key, 0, packet.encrypted_nothing, &hash)?; + + // responder.hash = HASH(responder.hash || msg.encrypted_nothing) + // hash = b2s_hash(hash, buf[ENC_NOTHING_OFF..ENC_NOTHING_OFF + + // ENC_NOTHING_SZ]); + + // Derive keys + // temp1 = HMAC(initiator.chaining_key, [empty]) + // temp2 = HMAC(temp1, 0x1) + // temp3 = HMAC(temp1, temp2 || 0x2) + // initiator.sending_key = temp2 + // initiator.receiving_key = temp3 + // initiator.sending_key_counter = 0 + // initiator.receiving_key_counter = 0 + let temp1 = b2s_hmac(&chaining_key, &[]); + let temp2 = b2s_hmac(&temp1, &[0x01]); + let temp3 = b2s_hmac2(&temp1, &temp2, &[0x02]); + + let rtt_time = Instant::now().duration_since(state.time_sent); + self.last_rtt = Some(rtt_time.as_millis() as u32); + + if is_previous { + self.previous = HandshakeState::None; + } else { + self.state = HandshakeState::None; + } + Ok(Session::new(local_index, peer_index, temp3, temp2)) + } + + pub(super) fn receive_cookie_reply( + &mut self, + packet: PacketCookieReply, + ) -> Result<(), WireGuardError> { + let mac1 = match self.cookies.last_mac1 { + Some(mac) => mac, + None => return Err(WireGuardError::UnexpectedPacket), + }; + + let local_index = self.cookies.index; + if packet.receiver_idx != local_index { + return Err(WireGuardError::WrongIndex) + } + // msg.encrypted_cookie = XAEAD(HASH(LABEL_COOKIE || responder.static_public), + // msg.nonce, cookie, last_received_msg.mac1) + let key = b2s_hash(LABEL_COOKIE, self.params.peer_static_public.as_bytes()); // TODO: pre-compute + + let payload = Payload { + aad: &mac1[0..16], + msg: packet.encrypted_cookie, + }; + let plaintext = XChaCha20Poly1305::new_from_slice(&key) + .unwrap() + .decrypt(packet.nonce.into(), payload) + .map_err(|_| WireGuardError::InvalidAeadTag)?; + + let cookie = plaintext + .try_into() + .map_err(|_| WireGuardError::InvalidPacket)?; + self.cookies.write_cookie = Some(cookie); + Ok(()) + } + + // Compute and append mac1 and mac2 to a handshake message + fn append_mac1_and_mac2<'a>( + &mut self, + local_index: u32, + dst: &'a mut [u8], + ) -> Result<&'a mut [u8], WireGuardError> { + let mac1_off = dst.len() - 32; + let mac2_off = dst.len() - 16; + + // msg.mac1 = MAC(HASH(LABEL_MAC1 || responder.static_public), + // msg[0:offsetof(msg.mac1)]) + let msg_mac1 = b2s_keyed_mac_16(&self.params.sending_mac1_key, &dst[..mac1_off]); + + dst[mac1_off..mac2_off].copy_from_slice(&msg_mac1[..]); + + // msg.mac2 = MAC(initiator.last_received_cookie, msg[0:offsetof(msg.mac2)]) + let msg_mac2: [u8; 16] = if let Some(cookie) = self.cookies.write_cookie { + b2s_keyed_mac_16(&cookie, &dst[..mac2_off]) + } else { + [0u8; 16] + }; + + dst[mac2_off..].copy_from_slice(&msg_mac2[..]); + + self.cookies.index = local_index; + self.cookies.last_mac1 = Some(msg_mac1); + Ok(dst) + } + + pub(super) fn format_handshake_initiation<'a>( + &mut self, + dst: &'a mut [u8], + ) -> Result<&'a mut [u8], WireGuardError> { + if dst.len() < super::HANDSHAKE_INIT_SZ { + return Err(WireGuardError::DestinationBufferTooSmall) + } + + let (message_type, rest) = dst.split_at_mut(4); + let (sender_index, rest) = rest.split_at_mut(4); + let (unencrypted_ephemeral, rest) = rest.split_at_mut(32); + let (encrypted_static, rest) = rest.split_at_mut(32 + 16); + let (encrypted_timestamp, _) = rest.split_at_mut(12 + 16); + + let local_index = self.inc_index(); + + // initiator.chaining_key = HASH(CONSTRUCTION) + let mut chaining_key = INITIAL_CHAIN_KEY; + // initiator.hash = HASH(HASH(initiator.chaining_key || IDENTIFIER) || + // responder.static_public) + let mut hash = INITIAL_CHAIN_HASH; + hash = b2s_hash(&hash, self.params.peer_static_public.as_bytes()); + // initiator.ephemeral_private = DH_GENERATE() + let ephemeral_private = x25519::ReusableSecret::random_from_rng(OsRng); + // msg.message_type = 1 + // msg.reserved_zero = { 0, 0, 0 } + message_type.copy_from_slice(&super::HANDSHAKE_INIT.to_le_bytes()); + // msg.sender_index = little_endian(initiator.sender_index) + sender_index.copy_from_slice(&local_index.to_le_bytes()); + // msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private) + unencrypted_ephemeral + .copy_from_slice(x25519::PublicKey::from(&ephemeral_private).as_bytes()); + // initiator.hash = HASH(initiator.hash || msg.unencrypted_ephemeral) + hash = b2s_hash(&hash, unencrypted_ephemeral); + // temp = HMAC(initiator.chaining_key, msg.unencrypted_ephemeral) + // initiator.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&b2s_hmac(&chaining_key, unencrypted_ephemeral), &[0x01]); + // temp = HMAC(initiator.chaining_key, DH(initiator.ephemeral_private, + // responder.static_public)) + let ephemeral_shared = ephemeral_private.diffie_hellman(&self.params.peer_static_public); + let temp = b2s_hmac(&chaining_key, &ephemeral_shared.to_bytes()); + // initiator.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // key = HMAC(temp, initiator.chaining_key || 0x2) + let key = b2s_hmac2(&temp, &chaining_key, &[0x02]); + // msg.encrypted_static = AEAD(key, 0, initiator.static_public, initiator.hash) + aead_chacha20_seal( + encrypted_static, + &key, + 0, + self.params.static_public.as_bytes(), + &hash, + ); + // initiator.hash = HASH(initiator.hash || msg.encrypted_static) + hash = b2s_hash(&hash, encrypted_static); + // temp = HMAC(initiator.chaining_key, DH(initiator.static_private, + // responder.static_public)) + let temp = b2s_hmac(&chaining_key, self.params.static_shared.as_bytes()); + // initiator.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // key = HMAC(temp, initiator.chaining_key || 0x2) + let key = b2s_hmac2(&temp, &chaining_key, &[0x02]); + // msg.encrypted_timestamp = AEAD(key, 0, TAI64N(), initiator.hash) + let timestamp = self.stamper.stamp(); + aead_chacha20_seal(encrypted_timestamp, &key, 0, ×tamp, &hash); + // initiator.hash = HASH(initiator.hash || msg.encrypted_timestamp) + hash = b2s_hash(&hash, encrypted_timestamp); + + let time_now = Instant::now(); + self.previous = std::mem::replace( + &mut self.state, + HandshakeState::InitSent(HandshakeInitSentState { + local_index, + chaining_key, + hash, + ephemeral_private, + time_sent: time_now, + }), + ); + + self.append_mac1_and_mac2(local_index, &mut dst[..super::HANDSHAKE_INIT_SZ]) + } + + fn format_handshake_response<'a>( + &mut self, + dst: &'a mut [u8], + ) -> Result<(&'a mut [u8], Session), WireGuardError> { + if dst.len() < super::HANDSHAKE_RESP_SZ { + return Err(WireGuardError::DestinationBufferTooSmall) + } + + let state = std::mem::replace(&mut self.state, HandshakeState::None); + let (mut chaining_key, mut hash, peer_ephemeral_public, peer_index) = match state { + HandshakeState::InitReceived { + chaining_key, + hash, + peer_ephemeral_public, + peer_index, + } => (chaining_key, hash, peer_ephemeral_public, peer_index), + _ => { + panic!("Unexpected attempt to call send_handshake_response"); + } + }; + + let (message_type, rest) = dst.split_at_mut(4); + let (sender_index, rest) = rest.split_at_mut(4); + let (receiver_index, rest) = rest.split_at_mut(4); + let (unencrypted_ephemeral, rest) = rest.split_at_mut(32); + let (encrypted_nothing, _) = rest.split_at_mut(16); + + // responder.ephemeral_private = DH_GENERATE() + let ephemeral_private = x25519::ReusableSecret::random_from_rng(OsRng); + let local_index = self.inc_index(); + // msg.message_type = 2 + // msg.reserved_zero = { 0, 0, 0 } + message_type.copy_from_slice(&super::HANDSHAKE_RESP.to_le_bytes()); + // msg.sender_index = little_endian(responder.sender_index) + sender_index.copy_from_slice(&local_index.to_le_bytes()); + // msg.receiver_index = little_endian(initiator.sender_index) + receiver_index.copy_from_slice(&peer_index.to_le_bytes()); + // msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private) + unencrypted_ephemeral + .copy_from_slice(x25519::PublicKey::from(&ephemeral_private).as_bytes()); + // responder.hash = HASH(responder.hash || msg.unencrypted_ephemeral) + hash = b2s_hash(&hash, unencrypted_ephemeral); + // temp = HMAC(responder.chaining_key, msg.unencrypted_ephemeral) + let temp = b2s_hmac(&chaining_key, unencrypted_ephemeral); + // responder.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // temp = HMAC(responder.chaining_key, DH(responder.ephemeral_private, + // initiator.ephemeral_public)) + let ephemeral_shared = ephemeral_private.diffie_hellman(&peer_ephemeral_public); + let temp = b2s_hmac(&chaining_key, &ephemeral_shared.to_bytes()); + // responder.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // temp = HMAC(responder.chaining_key, DH(responder.ephemeral_private, + // initiator.static_public)) + let temp = b2s_hmac( + &chaining_key, + &ephemeral_private + .diffie_hellman(&self.params.peer_static_public) + .to_bytes(), + ); + // responder.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // temp = HMAC(responder.chaining_key, preshared_key) + let temp = b2s_hmac( + &chaining_key, + &self.params.preshared_key.unwrap_or([0u8; 32])[..], + ); + // responder.chaining_key = HMAC(temp, 0x1) + chaining_key = b2s_hmac(&temp, &[0x01]); + // temp2 = HMAC(temp, responder.chaining_key || 0x2) + let temp2 = b2s_hmac2(&temp, &chaining_key, &[0x02]); + // key = HMAC(temp, temp2 || 0x3) + let key = b2s_hmac2(&temp, &temp2, &[0x03]); + // responder.hash = HASH(responder.hash || temp2) + hash = b2s_hash(&hash, &temp2); + // msg.encrypted_nothing = AEAD(key, 0, [empty], responder.hash) + aead_chacha20_seal(encrypted_nothing, &key, 0, &[], &hash); + + // Derive keys + // temp1 = HMAC(initiator.chaining_key, [empty]) + // temp2 = HMAC(temp1, 0x1) + // temp3 = HMAC(temp1, temp2 || 0x2) + // initiator.sending_key = temp2 + // initiator.receiving_key = temp3 + // initiator.sending_key_counter = 0 + // initiator.receiving_key_counter = 0 + let temp1 = b2s_hmac(&chaining_key, &[]); + let temp2 = b2s_hmac(&temp1, &[0x01]); + let temp3 = b2s_hmac2(&temp1, &temp2, &[0x02]); + + let dst = self.append_mac1_and_mac2(local_index, &mut dst[..super::HANDSHAKE_RESP_SZ])?; + + Ok((dst, Session::new(local_index, peer_index, temp2, temp3))) + } +} diff --git a/burrow/src/wireguard/noise/mod.rs b/burrow/src/wireguard/noise/mod.rs new file mode 100755 index 0000000..aa06652 --- /dev/null +++ b/burrow/src/wireguard/noise/mod.rs @@ -0,0 +1,632 @@ +// Copyright (c) 2019 Cloudflare, Inc. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause + +pub mod errors; +pub mod handshake; +pub mod rate_limiter; + +mod session; +mod timers; + +use std::{ + collections::VecDeque, + convert::{TryFrom, TryInto}, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + sync::Arc, + time::Duration, +}; + +use errors::WireGuardError; +use handshake::Handshake; +use rate_limiter::RateLimiter; +use timers::{TimerName, Timers}; + +/// The default value to use for rate limiting, when no other rate limiter is +/// defined +const PEER_HANDSHAKE_RATE_LIMIT: u64 = 10; + +const IPV4_MIN_HEADER_SIZE: usize = 20; +const IPV4_LEN_OFF: usize = 2; +const IPV4_SRC_IP_OFF: usize = 12; +const IPV4_DST_IP_OFF: usize = 16; +const IPV4_IP_SZ: usize = 4; + +const IPV6_MIN_HEADER_SIZE: usize = 40; +const IPV6_LEN_OFF: usize = 4; +const IPV6_SRC_IP_OFF: usize = 8; +const IPV6_DST_IP_OFF: usize = 24; +const IPV6_IP_SZ: usize = 16; + +const IP_LEN_SZ: usize = 2; + +const MAX_QUEUE_DEPTH: usize = 256; +/// number of sessions in the ring, better keep a PoT +const N_SESSIONS: usize = 8; + +pub mod x25519 { + pub use x25519_dalek::{PublicKey, ReusableSecret, SharedSecret, StaticSecret}; +} + +#[derive(Debug)] +pub enum TunnResult<'a> { + Done, + Err(WireGuardError), + WriteToNetwork(&'a mut [u8]), + WriteToTunnelV4(&'a mut [u8], Ipv4Addr), + WriteToTunnelV6(&'a mut [u8], Ipv6Addr), +} + +impl<'a> From for TunnResult<'a> { + fn from(err: WireGuardError) -> TunnResult<'a> { + TunnResult::Err(err) + } +} + +/// Tunnel represents a point-to-point WireGuard connection +#[derive(Debug)] +pub struct Tunnel { + /// The handshake currently in progress + handshake: handshake::Handshake, + /// The N_SESSIONS most recent sessions, index is session id modulo + /// N_SESSIONS + sessions: [Option; N_SESSIONS], + /// Index of most recently used session + current: usize, + /// Queue to store blocked packets + packet_queue: VecDeque>, + /// Keeps tabs on the expiring timers + timers: timers::Timers, + tx_bytes: usize, + rx_bytes: usize, + rate_limiter: Arc, +} + +type MessageType = u32; +const HANDSHAKE_INIT: MessageType = 1; +const HANDSHAKE_RESP: MessageType = 2; +const COOKIE_REPLY: MessageType = 3; +const DATA: MessageType = 4; + +const HANDSHAKE_INIT_SZ: usize = 148; +const HANDSHAKE_RESP_SZ: usize = 92; +const COOKIE_REPLY_SZ: usize = 64; +const DATA_OVERHEAD_SZ: usize = 32; + +#[derive(Debug)] +pub struct HandshakeInit<'a> { + sender_idx: u32, + unencrypted_ephemeral: &'a [u8; 32], + encrypted_static: &'a [u8], + encrypted_timestamp: &'a [u8], +} + +#[derive(Debug)] +pub struct HandshakeResponse<'a> { + sender_idx: u32, + pub receiver_idx: u32, + unencrypted_ephemeral: &'a [u8; 32], + encrypted_nothing: &'a [u8], +} + +#[derive(Debug)] +pub struct PacketCookieReply<'a> { + pub receiver_idx: u32, + nonce: &'a [u8], + encrypted_cookie: &'a [u8], +} + +#[derive(Debug)] +pub struct PacketData<'a> { + pub receiver_idx: u32, + counter: u64, + encrypted_encapsulated_packet: &'a [u8], +} + +/// Describes a packet from network +#[derive(Debug)] +pub enum Packet<'a> { + HandshakeInit(HandshakeInit<'a>), + HandshakeResponse(HandshakeResponse<'a>), + CookieReply(PacketCookieReply<'a>), + Data(PacketData<'a>), +} + +impl Tunnel { + #[inline(always)] + pub fn parse_incoming_packet(src: &[u8]) -> Result { + if src.len() < 4 { + return Err(WireGuardError::InvalidPacket) + } + + // Checks the type, as well as the reserved zero fields + let packet_type = u32::from_le_bytes(src[0..4].try_into().unwrap()); + tracing::debug!("packet_type: {}", packet_type); + + Ok(match (packet_type, src.len()) { + (HANDSHAKE_INIT, HANDSHAKE_INIT_SZ) => Packet::HandshakeInit(HandshakeInit { + sender_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()), + unencrypted_ephemeral: <&[u8; 32] as TryFrom<&[u8]>>::try_from(&src[8..40]) + .expect("length already checked above"), + encrypted_static: &src[40..88], + encrypted_timestamp: &src[88..116], + }), + (HANDSHAKE_RESP, HANDSHAKE_RESP_SZ) => Packet::HandshakeResponse(HandshakeResponse { + sender_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()), + receiver_idx: u32::from_le_bytes(src[8..12].try_into().unwrap()), + unencrypted_ephemeral: <&[u8; 32] as TryFrom<&[u8]>>::try_from(&src[12..44]) + .expect("length already checked above"), + encrypted_nothing: &src[44..60], + }), + (COOKIE_REPLY, COOKIE_REPLY_SZ) => Packet::CookieReply(PacketCookieReply { + receiver_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()), + nonce: &src[8..32], + encrypted_cookie: &src[32..64], + }), + (DATA, DATA_OVERHEAD_SZ..=std::usize::MAX) => Packet::Data(PacketData { + receiver_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()), + counter: u64::from_le_bytes(src[8..16].try_into().unwrap()), + encrypted_encapsulated_packet: &src[16..], + }), + _ => return Err(WireGuardError::InvalidPacket), + }) + } + + pub fn is_expired(&self) -> bool { + self.handshake.is_expired() + } + + pub fn dst_address(packet: &[u8]) -> Option { + if packet.is_empty() { + return None + } + + match packet[0] >> 4 { + 4 if packet.len() >= IPV4_MIN_HEADER_SIZE => { + let addr_bytes: [u8; IPV4_IP_SZ] = packet + [IPV4_DST_IP_OFF..IPV4_DST_IP_OFF + IPV4_IP_SZ] + .try_into() + .unwrap(); + Some(IpAddr::from(addr_bytes)) + } + 6 if packet.len() >= IPV6_MIN_HEADER_SIZE => { + let addr_bytes: [u8; IPV6_IP_SZ] = packet + [IPV6_DST_IP_OFF..IPV6_DST_IP_OFF + IPV6_IP_SZ] + .try_into() + .unwrap(); + Some(IpAddr::from(addr_bytes)) + } + _ => None, + } + } + + pub fn src_address(packet: &[u8]) -> Option { + if packet.is_empty() { + return None + } + + match packet[0] >> 4 { + 4 if packet.len() >= IPV4_MIN_HEADER_SIZE => { + let addr_bytes: [u8; IPV4_IP_SZ] = packet + [IPV4_SRC_IP_OFF..IPV4_SRC_IP_OFF + IPV4_IP_SZ] + .try_into() + .unwrap(); + Some(IpAddr::from(addr_bytes)) + } + 6 if packet.len() >= IPV6_MIN_HEADER_SIZE => { + let addr_bytes: [u8; IPV6_IP_SZ] = packet + [IPV6_SRC_IP_OFF..IPV6_SRC_IP_OFF + IPV6_IP_SZ] + .try_into() + .unwrap(); + Some(IpAddr::from(addr_bytes)) + } + _ => None, + } + } + + /// Create a new tunnel using own private key and the peer public key + pub fn new( + static_private: x25519::StaticSecret, + peer_static_public: x25519::PublicKey, + preshared_key: Option<[u8; 32]>, + persistent_keepalive: Option, + index: u32, + rate_limiter: Option>, + ) -> Result { + let static_public = x25519::PublicKey::from(&static_private); + + let tunn = Tunnel { + handshake: Handshake::new( + static_private, + static_public, + peer_static_public, + index << 8, + preshared_key, + ) + .map_err(|_| "Invalid parameters")?, + sessions: Default::default(), + current: Default::default(), + tx_bytes: Default::default(), + rx_bytes: Default::default(), + + packet_queue: VecDeque::new(), + timers: Timers::new(persistent_keepalive, rate_limiter.is_none()), + + rate_limiter: rate_limiter.unwrap_or_else(|| { + Arc::new(RateLimiter::new(&static_public, PEER_HANDSHAKE_RATE_LIMIT)) + }), + }; + + Ok(tunn) + } + + /// Update the private key and clear existing sessions + pub fn set_static_private( + &mut self, + static_private: x25519::StaticSecret, + static_public: x25519::PublicKey, + rate_limiter: Option>, + ) -> Result<(), WireGuardError> { + self.timers.should_reset_rr = rate_limiter.is_none(); + self.rate_limiter = rate_limiter.unwrap_or_else(|| { + Arc::new(RateLimiter::new(&static_public, PEER_HANDSHAKE_RATE_LIMIT)) + }); + self.handshake + .set_static_private(static_private, static_public)?; + for s in &mut self.sessions { + *s = None; + } + Ok(()) + } + + /// Encapsulate a single packet from the tunnel interface. + /// Returns TunnResult. + /// + /// # Panics + /// Panics if dst buffer is too small. + /// Size of dst should be at least src.len() + 32, and no less than 148 + /// bytes. + pub fn encapsulate<'a>(&mut self, src: &[u8], dst: &'a mut [u8]) -> TunnResult<'a> { + let current = self.current; + if let Some(ref session) = self.sessions[current % N_SESSIONS] { + // Send the packet using an established session + let packet = session.format_packet_data(src, dst); + self.timer_tick(TimerName::TimeLastPacketSent); + // Exclude Keepalive packets from timer update. + if !src.is_empty() { + self.timer_tick(TimerName::TimeLastDataPacketSent); + } + self.tx_bytes += src.len(); + return TunnResult::WriteToNetwork(packet) + } + + // If there is no session, queue the packet for future retry + self.queue_packet(src); + // Initiate a new handshake if none is in progress + self.format_handshake_initiation(dst, false) + } + + /// Receives a UDP datagram from the network and parses it. + /// Returns TunnResult. + /// + /// If the result is of type TunnResult::WriteToNetwork, should repeat the + /// call with empty datagram, until TunnResult::Done is returned. If + /// batch processing packets, it is OK to defer until last + /// packet is processed. + pub fn decapsulate<'a>( + &mut self, + src_addr: Option, + datagram: &[u8], + dst: &'a mut [u8], + ) -> TunnResult<'a> { + if datagram.is_empty() { + // Indicates a repeated call + return self.send_queued_packet(dst) + } + + let mut cookie = [0u8; COOKIE_REPLY_SZ]; + let packet = match self + .rate_limiter + .verify_packet(src_addr, datagram, &mut cookie) + { + Ok(packet) => packet, + Err(TunnResult::WriteToNetwork(cookie)) => { + dst[..cookie.len()].copy_from_slice(cookie); + return TunnResult::WriteToNetwork(&mut dst[..cookie.len()]) + } + Err(TunnResult::Err(e)) => return TunnResult::Err(e), + _ => unreachable!(), + }; + + self.handle_verified_packet(packet, dst) + } + + pub fn reset_rate_limiter(&self) { + self.rate_limiter.reset_count(); + } + + pub(crate) fn handle_verified_packet<'a>( + &mut self, + packet: Packet, + dst: &'a mut [u8], + ) -> TunnResult<'a> { + match packet { + Packet::HandshakeInit(p) => self.handle_handshake_init(p, dst), + Packet::HandshakeResponse(p) => self.handle_handshake_response(p, dst), + Packet::CookieReply(p) => self.handle_cookie_reply(p), + Packet::Data(p) => self.handle_data(p, dst), + } + .unwrap_or_else(TunnResult::from) + } + + fn handle_handshake_init<'a>( + &mut self, + p: HandshakeInit, + dst: &'a mut [u8], + ) -> Result, WireGuardError> { + tracing::debug!( + message = "Received handshake_initiation", + remote_idx = p.sender_idx + ); + + let (packet, session) = self.handshake.receive_handshake_initialization(p, dst)?; + + // Store new session in ring buffer + let index = session.local_index(); + self.sessions[index % N_SESSIONS] = Some(session); + + self.timer_tick(TimerName::TimeLastPacketReceived); + self.timer_tick(TimerName::TimeLastPacketSent); + self.timer_tick_session_established(false, index); // New session established, we are not the initiator + + tracing::debug!(message = "Sending handshake_response", local_idx = index); + + Ok(TunnResult::WriteToNetwork(packet)) + } + + fn handle_handshake_response<'a>( + &mut self, + p: HandshakeResponse, + dst: &'a mut [u8], + ) -> Result, WireGuardError> { + tracing::debug!( + message = "Received handshake_response", + local_idx = p.receiver_idx, + remote_idx = p.sender_idx + ); + + let session = self.handshake.receive_handshake_response(p)?; + + let keepalive_packet = session.format_packet_data(&[], dst); + // Store new session in ring buffer + let l_idx = session.local_index(); + let index = l_idx % N_SESSIONS; + self.sessions[index] = Some(session); + + self.timer_tick(TimerName::TimeLastPacketReceived); + self.timer_tick_session_established(true, index); // New session established, we are the initiator + self.set_current_session(l_idx); + + tracing::debug!("Sending keepalive"); + + Ok(TunnResult::WriteToNetwork(keepalive_packet)) // Send a keepalive as + // a response + } + + fn handle_cookie_reply<'a>( + &mut self, + p: PacketCookieReply, + ) -> Result, WireGuardError> { + tracing::debug!( + message = "Received cookie_reply", + local_idx = p.receiver_idx + ); + + self.handshake.receive_cookie_reply(p)?; + self.timer_tick(TimerName::TimeLastPacketReceived); + self.timer_tick(TimerName::TimeCookieReceived); + + tracing::debug!("Did set cookie"); + + Ok(TunnResult::Done) + } + + /// Update the index of the currently used session, if needed + fn set_current_session(&mut self, new_idx: usize) { + let cur_idx = self.current; + if cur_idx == new_idx { + // There is nothing to do, already using this session, this is the common case + return + } + if self.sessions[cur_idx % N_SESSIONS].is_none() + || self.timers.session_timers[new_idx % N_SESSIONS] + >= self.timers.session_timers[cur_idx % N_SESSIONS] + { + self.current = new_idx; + tracing::debug!(message = "New session", session = new_idx); + } + } + + /// Decrypts a data packet, and stores the decapsulated packet in dst. + fn handle_data<'a>( + &mut self, + packet: PacketData, + dst: &'a mut [u8], + ) -> Result, WireGuardError> { + let r_idx = packet.receiver_idx as usize; + let idx = r_idx % N_SESSIONS; + + // Get the (probably) right session + let decapsulated_packet = { + let session = self.sessions[idx].as_ref(); + let session = session.ok_or_else(|| { + tracing::trace!(message = "No current session available", remote_idx = r_idx); + WireGuardError::NoCurrentSession + })?; + session.receive_packet_data(packet, dst)? + }; + + self.set_current_session(r_idx); + + self.timer_tick(TimerName::TimeLastPacketReceived); + + Ok(self.validate_decapsulated_packet(decapsulated_packet)) + } + + /// Formats a new handshake initiation message and store it in dst. If + /// force_resend is true will send a new handshake, even if a handshake + /// is already in progress (for example when a handshake times out) + pub fn format_handshake_initiation<'a>( + &mut self, + dst: &'a mut [u8], + force_resend: bool, + ) -> TunnResult<'a> { + if self.handshake.is_in_progress() && !force_resend { + return TunnResult::Done + } + + if self.handshake.is_expired() { + self.timers.clear(); + } + + let starting_new_handshake = !self.handshake.is_in_progress(); + + match self.handshake.format_handshake_initiation(dst) { + Ok(packet) => { + tracing::debug!("Sending handshake_initiation"); + + if starting_new_handshake { + self.timer_tick(TimerName::TimeLastHandshakeStarted); + } + self.timer_tick(TimerName::TimeLastPacketSent); + TunnResult::WriteToNetwork(packet) + } + Err(e) => TunnResult::Err(e), + } + } + + /// Check if an IP packet is v4 or v6, truncate to the length indicated by + /// the length field Returns the truncated packet and the source IP as + /// TunnResult + fn validate_decapsulated_packet<'a>(&mut self, packet: &'a mut [u8]) -> TunnResult<'a> { + let (computed_len, src_ip_address) = match packet.len() { + 0 => return TunnResult::Done, // This is keepalive, and not an error + _ if packet[0] >> 4 == 4 && packet.len() >= IPV4_MIN_HEADER_SIZE => { + let len_bytes: [u8; IP_LEN_SZ] = packet[IPV4_LEN_OFF..IPV4_LEN_OFF + IP_LEN_SZ] + .try_into() + .unwrap(); + let addr_bytes: [u8; IPV4_IP_SZ] = packet + [IPV4_SRC_IP_OFF..IPV4_SRC_IP_OFF + IPV4_IP_SZ] + .try_into() + .unwrap(); + ( + u16::from_be_bytes(len_bytes) as usize, + IpAddr::from(addr_bytes), + ) + } + _ if packet[0] >> 4 == 6 && packet.len() >= IPV6_MIN_HEADER_SIZE => { + let len_bytes: [u8; IP_LEN_SZ] = packet[IPV6_LEN_OFF..IPV6_LEN_OFF + IP_LEN_SZ] + .try_into() + .unwrap(); + let addr_bytes: [u8; IPV6_IP_SZ] = packet + [IPV6_SRC_IP_OFF..IPV6_SRC_IP_OFF + IPV6_IP_SZ] + .try_into() + .unwrap(); + ( + u16::from_be_bytes(len_bytes) as usize + IPV6_MIN_HEADER_SIZE, + IpAddr::from(addr_bytes), + ) + } + _ => return TunnResult::Err(WireGuardError::InvalidPacket), + }; + + if computed_len > packet.len() { + return TunnResult::Err(WireGuardError::InvalidPacket) + } + + self.timer_tick(TimerName::TimeLastDataPacketReceived); + self.rx_bytes += computed_len; + + match src_ip_address { + IpAddr::V4(addr) => TunnResult::WriteToTunnelV4(&mut packet[..computed_len], addr), + IpAddr::V6(addr) => TunnResult::WriteToTunnelV6(&mut packet[..computed_len], addr), + } + } + + /// Get a packet from the queue, and try to encapsulate it + fn send_queued_packet<'a>(&mut self, dst: &'a mut [u8]) -> TunnResult<'a> { + if let Some(packet) = self.dequeue_packet() { + match self.encapsulate(&packet, dst) { + TunnResult::Err(_) => { + // On error, return packet to the queue + self.requeue_packet(packet); + } + r => return r, + } + } + TunnResult::Done + } + + /// Push packet to the back of the queue + fn queue_packet(&mut self, packet: &[u8]) { + if self.packet_queue.len() < MAX_QUEUE_DEPTH { + // Drop if too many are already in queue + self.packet_queue.push_back(packet.to_vec()); + } + } + + /// Push packet to the front of the queue + fn requeue_packet(&mut self, packet: Vec) { + if self.packet_queue.len() < MAX_QUEUE_DEPTH { + // Drop if too many are already in queue + self.packet_queue.push_front(packet); + } + } + + fn dequeue_packet(&mut self) -> Option> { + self.packet_queue.pop_front() + } + + fn estimate_loss(&self) -> f32 { + let session_idx = self.current; + + let mut weight = 9.0; + let mut cur_avg = 0.0; + let mut total_weight = 0.0; + + for i in 0..N_SESSIONS { + if let Some(ref session) = self.sessions[(session_idx.wrapping_sub(i)) % N_SESSIONS] { + let (expected, received) = session.current_packet_cnt(); + + let loss = if expected == 0 { + 0.0 + } else { + 1.0 - received as f32 / expected as f32 + }; + + cur_avg += loss * weight; + total_weight += weight; + weight /= 3.0; + } + } + + if total_weight == 0.0 { + 0.0 + } else { + cur_avg / total_weight + } + } + + /// Return stats from the tunnel: + /// * Time since last handshake in seconds + /// * Data bytes sent + /// * Data bytes received + pub fn stats(&self) -> (Option, usize, usize, f32, Option) { + let time = self.time_since_last_handshake(); + let tx_bytes = self.tx_bytes; + let rx_bytes = self.rx_bytes; + let loss = self.estimate_loss(); + let rtt = self.handshake.last_rtt; + + (time, tx_bytes, rx_bytes, loss, rtt) + } +} diff --git a/burrow/src/wireguard/noise/rate_limiter.rs b/burrow/src/wireguard/noise/rate_limiter.rs new file mode 100755 index 0000000..ff19efd --- /dev/null +++ b/burrow/src/wireguard/noise/rate_limiter.rs @@ -0,0 +1,212 @@ +use std::{ + net::IpAddr, + sync::atomic::{AtomicU64, Ordering}, + time::Instant, +}; + +use aead::{generic_array::GenericArray, AeadInPlace, KeyInit}; +use chacha20poly1305::{Key, XChaCha20Poly1305}; +use parking_lot::Mutex; +use rand_core::{OsRng, RngCore}; +use ring::constant_time::verify_slices_are_equal; + +use super::{ + handshake::{ + b2s_hash, + b2s_keyed_mac_16, + b2s_keyed_mac_16_2, + b2s_mac_24, + LABEL_COOKIE, + LABEL_MAC1, + }, + HandshakeInit, + HandshakeResponse, + Packet, + TunnResult, + Tunnel, + WireGuardError, +}; + +const COOKIE_REFRESH: u64 = 128; // Use 128 and not 120 so the compiler can optimize out the division +const COOKIE_SIZE: usize = 16; +const COOKIE_NONCE_SIZE: usize = 24; + +/// How often should reset count in seconds +const RESET_PERIOD: u64 = 1; + +type Cookie = [u8; COOKIE_SIZE]; + +/// There are two places where WireGuard requires "randomness" for cookies +/// * The 24 byte nonce in the cookie massage - here the only goal is to avoid +/// nonce reuse +/// * A secret value that changes every two minutes +/// Because the main goal of the cookie is simply for a party to prove ownership +/// of an IP address we can relax the randomness definition a bit, in order to +/// avoid locking, because using less resources is the main goal of any DoS +/// prevention mechanism. In order to avoid locking and calls to rand we derive +/// pseudo random values using the AEAD and some counters. +#[derive(Debug)] +pub struct RateLimiter { + /// The key we use to derive the nonce + nonce_key: [u8; 32], + /// The key we use to derive the cookie + secret_key: [u8; 16], + start_time: Instant, + /// A single 64 bit counter (should suffice for many years) + nonce_ctr: AtomicU64, + mac1_key: [u8; 32], + cookie_key: Key, + limit: u64, + /// The counter since last reset + count: AtomicU64, + /// The time last reset was performed on this rate limiter + last_reset: Mutex, +} + +impl RateLimiter { + pub fn new(public_key: &super::x25519::PublicKey, limit: u64) -> Self { + let mut secret_key = [0u8; 16]; + OsRng.fill_bytes(&mut secret_key); + RateLimiter { + nonce_key: Self::rand_bytes(), + secret_key, + start_time: Instant::now(), + nonce_ctr: AtomicU64::new(0), + mac1_key: b2s_hash(LABEL_MAC1, public_key.as_bytes()), + cookie_key: b2s_hash(LABEL_COOKIE, public_key.as_bytes()).into(), + limit, + count: AtomicU64::new(0), + last_reset: Mutex::new(Instant::now()), + } + } + + fn rand_bytes() -> [u8; 32] { + let mut key = [0u8; 32]; + OsRng.fill_bytes(&mut key); + key + } + + /// Reset packet count (ideally should be called with a period of 1 second) + pub fn reset_count(&self) { + // The rate limiter is not very accurate, but at the scale we care about it + // doesn't matter much + let current_time = Instant::now(); + let mut last_reset_time = self.last_reset.lock(); + if current_time.duration_since(*last_reset_time).as_secs() >= RESET_PERIOD { + self.count.store(0, Ordering::SeqCst); + *last_reset_time = current_time; + } + } + + /// Compute the correct cookie value based on the current secret value and + /// the source IP + fn current_cookie(&self, addr: IpAddr) -> Cookie { + let mut addr_bytes = [0u8; 16]; + + match addr { + IpAddr::V4(a) => addr_bytes[..4].copy_from_slice(&a.octets()[..]), + IpAddr::V6(a) => addr_bytes[..].copy_from_slice(&a.octets()[..]), + } + + // The current cookie for a given IP is the + // MAC(responder.changing_secret_every_two_minutes, initiator.ip_address) + // First we derive the secret from the current time, the value of cur_counter + // would change with time. + let cur_counter = Instant::now().duration_since(self.start_time).as_secs() / COOKIE_REFRESH; + + // Next we derive the cookie + b2s_keyed_mac_16_2(&self.secret_key, &cur_counter.to_le_bytes(), &addr_bytes) + } + + fn nonce(&self) -> [u8; COOKIE_NONCE_SIZE] { + let ctr = self.nonce_ctr.fetch_add(1, Ordering::Relaxed); + + b2s_mac_24(&self.nonce_key, &ctr.to_le_bytes()) + } + + fn is_under_load(&self) -> bool { + self.count.fetch_add(1, Ordering::SeqCst) >= self.limit + } + + pub(crate) fn format_cookie_reply<'a>( + &self, + idx: u32, + cookie: Cookie, + mac1: &[u8], + dst: &'a mut [u8], + ) -> Result<&'a mut [u8], WireGuardError> { + if dst.len() < super::COOKIE_REPLY_SZ { + return Err(WireGuardError::DestinationBufferTooSmall) + } + + let (message_type, rest) = dst.split_at_mut(4); + let (receiver_index, rest) = rest.split_at_mut(4); + let (nonce, rest) = rest.split_at_mut(24); + let (encrypted_cookie, _) = rest.split_at_mut(16 + 16); + + // msg.message_type = 3 + // msg.reserved_zero = { 0, 0, 0 } + message_type.copy_from_slice(&super::COOKIE_REPLY.to_le_bytes()); + // msg.receiver_index = little_endian(initiator.sender_index) + receiver_index.copy_from_slice(&idx.to_le_bytes()); + nonce.copy_from_slice(&self.nonce()[..]); + + let cipher = XChaCha20Poly1305::new(&self.cookie_key); + + let iv = GenericArray::from_slice(nonce); + + encrypted_cookie[..16].copy_from_slice(&cookie); + let tag = cipher + .encrypt_in_place_detached(iv, mac1, &mut encrypted_cookie[..16]) + .map_err(|_| WireGuardError::DestinationBufferTooSmall)?; + + encrypted_cookie[16..].copy_from_slice(&tag); + + Ok(&mut dst[..super::COOKIE_REPLY_SZ]) + } + + /// Verify the MAC fields on the datagram, and apply rate limiting if needed + pub fn verify_packet<'a, 'b>( + &self, + src_addr: Option, + src: &'a [u8], + dst: &'b mut [u8], + ) -> Result, TunnResult<'b>> { + let packet = Tunnel::parse_incoming_packet(src)?; + tracing::debug!("packet: {:?}", packet); + + // Verify and rate limit handshake messages only + if let Packet::HandshakeInit(HandshakeInit { sender_idx, .. }) + | Packet::HandshakeResponse(HandshakeResponse { sender_idx, .. }) = packet + { + tracing::debug!("sender_idx: {}", sender_idx); + tracing::debug!("response: {:?}", packet); + let (msg, macs) = src.split_at(src.len() - 32); + let (mac1, mac2) = macs.split_at(16); + + let computed_mac1 = b2s_keyed_mac_16(&self.mac1_key, msg); + verify_slices_are_equal(&computed_mac1[..16], mac1) + .map_err(|_| TunnResult::Err(WireGuardError::InvalidMac))?; + + if self.is_under_load() { + let addr = match src_addr { + None => return Err(TunnResult::Err(WireGuardError::UnderLoad)), + Some(addr) => addr, + }; + + // Only given an address can we validate mac2 + let cookie = self.current_cookie(addr); + let computed_mac2 = b2s_keyed_mac_16_2(&cookie, msg, mac1); + + if verify_slices_are_equal(&computed_mac2[..16], mac2).is_err() { + let cookie_packet = self + .format_cookie_reply(sender_idx, cookie, mac1, dst) + .map_err(TunnResult::Err)?; + return Err(TunnResult::WriteToNetwork(cookie_packet)) + } + } + } + + Ok(packet) + } +} diff --git a/burrow/src/wireguard/noise/session.rs b/burrow/src/wireguard/noise/session.rs new file mode 100755 index 0000000..8988728 --- /dev/null +++ b/burrow/src/wireguard/noise/session.rs @@ -0,0 +1,280 @@ +// Copyright (c) 2019 Cloudflare, Inc. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause + +use std::sync::atomic::{AtomicUsize, Ordering}; + +use parking_lot::Mutex; +use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305}; + +use super::{errors::WireGuardError, PacketData}; + +pub struct Session { + pub(crate) receiving_index: u32, + sending_index: u32, + receiver: LessSafeKey, + sender: LessSafeKey, + sending_key_counter: AtomicUsize, + receiving_key_counter: Mutex, +} + +impl std::fmt::Debug for Session { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Session: {}<- ->{}", + self.receiving_index, self.sending_index + ) + } +} + +/// Where encrypted data resides in a data packet +const DATA_OFFSET: usize = 16; +/// The overhead of the AEAD +const AEAD_SIZE: usize = 16; + +// Receiving buffer constants +const WORD_SIZE: u64 = 64; +const N_WORDS: u64 = 16; // Suffice to reorder 64*16 = 1024 packets; can be increased at will +const N_BITS: u64 = WORD_SIZE * N_WORDS; + +#[derive(Debug, Clone, Default)] +struct ReceivingKeyCounterValidator { + /// In order to avoid replays while allowing for some reordering of the + /// packets, we keep a bitmap of received packets, and the value of the + /// highest counter + next: u64, + /// Used to estimate packet loss + receive_cnt: u64, + bitmap: [u64; N_WORDS as usize], +} + +impl ReceivingKeyCounterValidator { + #[inline(always)] + fn set_bit(&mut self, idx: u64) { + let bit_idx = idx % N_BITS; + let word = (bit_idx / WORD_SIZE) as usize; + let bit = (bit_idx % WORD_SIZE) as usize; + self.bitmap[word] |= 1 << bit; + } + + #[inline(always)] + fn clear_bit(&mut self, idx: u64) { + let bit_idx = idx % N_BITS; + let word = (bit_idx / WORD_SIZE) as usize; + let bit = (bit_idx % WORD_SIZE) as usize; + self.bitmap[word] &= !(1u64 << bit); + } + + /// Clear the word that contains idx + #[inline(always)] + fn clear_word(&mut self, idx: u64) { + let bit_idx = idx % N_BITS; + let word = (bit_idx / WORD_SIZE) as usize; + self.bitmap[word] = 0; + } + + /// Returns true if bit is set, false otherwise + #[inline(always)] + fn check_bit(&self, idx: u64) -> bool { + let bit_idx = idx % N_BITS; + let word = (bit_idx / WORD_SIZE) as usize; + let bit = (bit_idx % WORD_SIZE) as usize; + ((self.bitmap[word] >> bit) & 1) == 1 + } + + /// Returns true if the counter was not yet received, and is not too far + /// back + #[inline(always)] + fn will_accept(&self, counter: u64) -> Result<(), WireGuardError> { + if counter >= self.next { + // As long as the counter is growing no replay took place for sure + return Ok(()) + } + if counter + N_BITS < self.next { + // Drop if too far back + return Err(WireGuardError::InvalidCounter) + } + if !self.check_bit(counter) { + Ok(()) + } else { + Err(WireGuardError::DuplicateCounter) + } + } + + /// Marks the counter as received, and returns true if it is still good (in + /// case during decryption something changed) + #[inline(always)] + fn mark_did_receive(&mut self, counter: u64) -> Result<(), WireGuardError> { + if counter + N_BITS < self.next { + // Drop if too far back + return Err(WireGuardError::InvalidCounter) + } + if counter == self.next { + // Usually the packets arrive in order, in that case we simply mark the bit and + // increment the counter + self.set_bit(counter); + self.next += 1; + return Ok(()) + } + if counter < self.next { + // A packet arrived out of order, check if it is valid, and mark + if self.check_bit(counter) { + return Err(WireGuardError::InvalidCounter) + } + self.set_bit(counter); + return Ok(()) + } + // Packets where dropped, or maybe reordered, skip them and mark unused + if counter - self.next >= N_BITS { + // Too far ahead, clear all the bits + for c in self.bitmap.iter_mut() { + *c = 0; + } + } else { + let mut i = self.next; + while i % WORD_SIZE != 0 && i < counter { + // Clear until i aligned to word size + self.clear_bit(i); + i += 1; + } + while i + WORD_SIZE < counter { + // Clear whole word at a time + self.clear_word(i); + i = (i + WORD_SIZE) & 0u64.wrapping_sub(WORD_SIZE); + } + while i < counter { + // Clear any remaining bits + self.clear_bit(i); + i += 1; + } + } + self.set_bit(counter); + self.next = counter + 1; + Ok(()) + } +} + +impl Session { + pub(super) fn new( + local_index: u32, + peer_index: u32, + receiving_key: [u8; 32], + sending_key: [u8; 32], + ) -> Session { + Session { + receiving_index: local_index, + sending_index: peer_index, + receiver: LessSafeKey::new( + UnboundKey::new(&CHACHA20_POLY1305, &receiving_key).unwrap(), + ), + sender: LessSafeKey::new(UnboundKey::new(&CHACHA20_POLY1305, &sending_key).unwrap()), + sending_key_counter: AtomicUsize::new(0), + receiving_key_counter: Mutex::new(Default::default()), + } + } + + pub(super) fn local_index(&self) -> usize { + self.receiving_index as usize + } + + /// Returns true if receiving counter is good to use + fn receiving_counter_quick_check(&self, counter: u64) -> Result<(), WireGuardError> { + let counter_validator = self.receiving_key_counter.lock(); + counter_validator.will_accept(counter) + } + + /// Returns true if receiving counter is good to use, and marks it as used { + fn receiving_counter_mark(&self, counter: u64) -> Result<(), WireGuardError> { + let mut counter_validator = self.receiving_key_counter.lock(); + let ret = counter_validator.mark_did_receive(counter); + if ret.is_ok() { + counter_validator.receive_cnt += 1; + } + ret + } + + /// src - an IP packet from the interface + /// dst - pre-allocated space to hold the encapsulating UDP packet to send + /// over the network returns the size of the formatted packet + pub(super) fn format_packet_data<'a>(&self, src: &[u8], dst: &'a mut [u8]) -> &'a mut [u8] { + if dst.len() < src.len() + super::DATA_OVERHEAD_SZ { + panic!("The destination buffer is too small"); + } + + let sending_key_counter = self.sending_key_counter.fetch_add(1, Ordering::Relaxed) as u64; + + let (message_type, rest) = dst.split_at_mut(4); + let (receiver_index, rest) = rest.split_at_mut(4); + let (counter, data) = rest.split_at_mut(8); + + message_type.copy_from_slice(&super::DATA.to_le_bytes()); + receiver_index.copy_from_slice(&self.sending_index.to_le_bytes()); + counter.copy_from_slice(&sending_key_counter.to_le_bytes()); + + // TODO: spec requires padding to 16 bytes, but actually works fine without it + let n = { + let mut nonce = [0u8; 12]; + nonce[4..12].copy_from_slice(&sending_key_counter.to_le_bytes()); + data[..src.len()].copy_from_slice(src); + self.sender + .seal_in_place_separate_tag( + Nonce::assume_unique_for_key(nonce), + Aad::from(&[]), + &mut data[..src.len()], + ) + .map(|tag| { + data[src.len()..src.len() + AEAD_SIZE].copy_from_slice(tag.as_ref()); + src.len() + AEAD_SIZE + }) + .unwrap() + }; + + &mut dst[..DATA_OFFSET + n] + } + + /// packet - a data packet we received from the network + /// dst - pre-allocated space to hold the encapsulated IP packet, to send to + /// the interface dst will always take less space than src + /// return the size of the encapsulated packet on success + pub(super) fn receive_packet_data<'a>( + &self, + packet: PacketData, + dst: &'a mut [u8], + ) -> Result<&'a mut [u8], WireGuardError> { + let ct_len = packet.encrypted_encapsulated_packet.len(); + if dst.len() < ct_len { + // This is a very incorrect use of the library, therefore panic and not error + panic!("The destination buffer is too small"); + } + if packet.receiver_idx != self.receiving_index { + return Err(WireGuardError::WrongIndex) + } + // Don't reuse counters, in case this is a replay attack we want to quickly + // check the counter without running expensive decryption + self.receiving_counter_quick_check(packet.counter)?; + + tracing::debug!("TAG C"); + let ret = { + let mut nonce = [0u8; 12]; + nonce[4..12].copy_from_slice(&packet.counter.to_le_bytes()); + dst[..ct_len].copy_from_slice(packet.encrypted_encapsulated_packet); + self.receiver + .open_in_place( + Nonce::assume_unique_for_key(nonce), + Aad::from(&[]), + &mut dst[..ct_len], + ) + .map_err(|_| WireGuardError::InvalidAeadTag)? + }; + + // After decryption is done, check counter again, and mark as received + self.receiving_counter_mark(packet.counter)?; + Ok(ret) + } + + /// Returns the estimated downstream packet loss for this session + pub(super) fn current_packet_cnt(&self) -> (u64, u64) { + let counter_validator = self.receiving_key_counter.lock(); + (counter_validator.next, counter_validator.receive_cnt) + } +} diff --git a/burrow/src/wireguard/noise/timers.rs b/burrow/src/wireguard/noise/timers.rs new file mode 100755 index 0000000..1d0cf1f --- /dev/null +++ b/burrow/src/wireguard/noise/timers.rs @@ -0,0 +1,333 @@ +// Copyright (c) 2019 Cloudflare, Inc. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause + +use std::{ + mem, + ops::{Index, IndexMut}, + time::{Duration, Instant}, +}; + +use super::{errors::WireGuardError, TunnResult, Tunnel}; + +// Some constants, represent time in seconds +// https://www.wireguard.com/papers/wireguard.pdf#page=14 +pub(crate) const REKEY_AFTER_TIME: Duration = Duration::from_secs(120); +const REJECT_AFTER_TIME: Duration = Duration::from_secs(180); +const REKEY_ATTEMPT_TIME: Duration = Duration::from_secs(90); +pub(crate) const REKEY_TIMEOUT: Duration = Duration::from_secs(5); +const KEEPALIVE_TIMEOUT: Duration = Duration::from_secs(10); +const COOKIE_EXPIRATION_TIME: Duration = Duration::from_secs(120); + +#[derive(Debug)] +pub enum TimerName { + /// Current time, updated each call to `update_timers` + TimeCurrent, + /// Time when last handshake was completed + TimeSessionEstablished, + /// Time the last attempt for a new handshake began + TimeLastHandshakeStarted, + /// Time we last received and authenticated a packet + TimeLastPacketReceived, + /// Time we last send a packet + TimeLastPacketSent, + /// Time we last received and authenticated a DATA packet + TimeLastDataPacketReceived, + /// Time we last send a DATA packet + TimeLastDataPacketSent, + /// Time we last received a cookie + TimeCookieReceived, + /// Time we last sent persistent keepalive + TimePersistentKeepalive, + Top, +} + +use self::TimerName::*; + +#[derive(Debug)] +pub struct Timers { + /// Is the owner of the timer the initiator or the responder for the last + /// handshake? + is_initiator: bool, + /// Start time of the tunnel + time_started: Instant, + timers: [Duration; TimerName::Top as usize], + pub(super) session_timers: [Duration; super::N_SESSIONS], + /// Did we receive data without sending anything back? + want_keepalive: bool, + /// Did we send data without hearing back? + want_handshake: bool, + persistent_keepalive: usize, + /// Should this timer call reset rr function (if not a shared rr instance) + pub(super) should_reset_rr: bool, +} + +impl Timers { + pub(super) fn new(persistent_keepalive: Option, reset_rr: bool) -> Timers { + Timers { + is_initiator: false, + time_started: Instant::now(), + timers: Default::default(), + session_timers: Default::default(), + want_keepalive: Default::default(), + want_handshake: Default::default(), + persistent_keepalive: usize::from(persistent_keepalive.unwrap_or(0)), + should_reset_rr: reset_rr, + } + } + + fn is_initiator(&self) -> bool { + self.is_initiator + } + + // We don't really clear the timers, but we set them to the current time to + // so the reference time frame is the same + pub(super) fn clear(&mut self) { + let now = Instant::now().duration_since(self.time_started); + for t in &mut self.timers[..] { + *t = now; + } + self.want_handshake = false; + self.want_keepalive = false; + } +} + +impl Index for Timers { + type Output = Duration; + + fn index(&self, index: TimerName) -> &Duration { + &self.timers[index as usize] + } +} + +impl IndexMut for Timers { + fn index_mut(&mut self, index: TimerName) -> &mut Duration { + &mut self.timers[index as usize] + } +} + +impl Tunnel { + pub(super) fn timer_tick(&mut self, timer_name: TimerName) { + match timer_name { + TimeLastPacketReceived => { + self.timers.want_keepalive = true; + self.timers.want_handshake = false; + } + TimeLastPacketSent => { + self.timers.want_handshake = true; + self.timers.want_keepalive = false; + } + _ => {} + } + + let time = self.timers[TimeCurrent]; + self.timers[timer_name] = time; + } + + pub(super) fn timer_tick_session_established( + &mut self, + is_initiator: bool, + session_idx: usize, + ) { + self.timer_tick(TimeSessionEstablished); + self.timers.session_timers[session_idx % super::N_SESSIONS] = self.timers[TimeCurrent]; + self.timers.is_initiator = is_initiator; + } + + // We don't really clear the timers, but we set them to the current time to + // so the reference time frame is the same + fn clear_all(&mut self) { + for session in &mut self.sessions { + *session = None; + } + + self.packet_queue.clear(); + + self.timers.clear(); + } + + fn update_session_timers(&mut self, time_now: Duration) { + let timers = &mut self.timers; + + for (i, t) in timers.session_timers.iter_mut().enumerate() { + if time_now - *t > REJECT_AFTER_TIME { + if let Some(session) = self.sessions[i].take() { + tracing::debug!( + message = "SESSION_EXPIRED(REJECT_AFTER_TIME)", + session = session.receiving_index + ); + } + *t = time_now; + } + } + } + + pub fn update_timers<'a>(&mut self, dst: &'a mut [u8]) -> TunnResult<'a> { + let mut handshake_initiation_required = false; + let mut keepalive_required = false; + + let time = Instant::now(); + + if self.timers.should_reset_rr { + self.rate_limiter.reset_count(); + } + + // All the times are counted from tunnel initiation, for efficiency our timers + // are rounded to a second, as there is no real benefit to having highly + // accurate timers. + let now = time.duration_since(self.timers.time_started); + self.timers[TimeCurrent] = now; + + self.update_session_timers(now); + + // Load timers only once: + let session_established = self.timers[TimeSessionEstablished]; + let handshake_started = self.timers[TimeLastHandshakeStarted]; + let aut_packet_received = self.timers[TimeLastPacketReceived]; + let aut_packet_sent = self.timers[TimeLastPacketSent]; + let data_packet_received = self.timers[TimeLastDataPacketReceived]; + let data_packet_sent = self.timers[TimeLastDataPacketSent]; + let persistent_keepalive = self.timers.persistent_keepalive; + + { + if self.handshake.is_expired() { + return TunnResult::Err(WireGuardError::ConnectionExpired) + } + + // Clear cookie after COOKIE_EXPIRATION_TIME + if self.handshake.has_cookie() + && now - self.timers[TimeCookieReceived] >= COOKIE_EXPIRATION_TIME + { + self.handshake.clear_cookie(); + } + + // All ephemeral private keys and symmetric session keys are zeroed out after + // (REJECT_AFTER_TIME * 3) ms if no new keys have been exchanged. + if now - session_established >= REJECT_AFTER_TIME * 3 { + tracing::error!("CONNECTION_EXPIRED(REJECT_AFTER_TIME * 3)"); + self.handshake.set_expired(); + self.clear_all(); + return TunnResult::Err(WireGuardError::ConnectionExpired) + } + + if let Some(time_init_sent) = self.handshake.timer() { + // Handshake Initiation Retransmission + if now - handshake_started >= REKEY_ATTEMPT_TIME { + // After REKEY_ATTEMPT_TIME ms of trying to initiate a new handshake, + // the retries give up and cease, and clear all existing packets queued + // up to be sent. If a packet is explicitly queued up to be sent, then + // this timer is reset. + tracing::error!("CONNECTION_EXPIRED(REKEY_ATTEMPT_TIME)"); + self.handshake.set_expired(); + self.clear_all(); + return TunnResult::Err(WireGuardError::ConnectionExpired) + } + + if time_init_sent.elapsed() >= REKEY_TIMEOUT { + // We avoid using `time` here, because it can be earlier than `time_init_sent`. + // Once `checked_duration_since` is stable we can use that. + // A handshake initiation is retried after REKEY_TIMEOUT + jitter ms, + // if a response has not been received, where jitter is some random + // value between 0 and 333 ms. + tracing::warn!("HANDSHAKE(REKEY_TIMEOUT)"); + handshake_initiation_required = true; + } + } else { + if self.timers.is_initiator() { + // After sending a packet, if the sender was the original initiator + // of the handshake and if the current session key is REKEY_AFTER_TIME + // ms old, we initiate a new handshake. If the sender was the original + // responder of the handshake, it does not re-initiate a new handshake + // after REKEY_AFTER_TIME ms like the original initiator does. + if session_established < data_packet_sent + && now - session_established >= REKEY_AFTER_TIME + { + tracing::debug!("HANDSHAKE(REKEY_AFTER_TIME (on send))"); + handshake_initiation_required = true; + } + + // After receiving a packet, if the receiver was the original initiator + // of the handshake and if the current session key is REJECT_AFTER_TIME + // - KEEPALIVE_TIMEOUT - REKEY_TIMEOUT ms old, we initiate a new + // handshake. + if session_established < data_packet_received + && now - session_established + >= REJECT_AFTER_TIME - KEEPALIVE_TIMEOUT - REKEY_TIMEOUT + { + tracing::warn!( + "HANDSHAKE(REJECT_AFTER_TIME - KEEPALIVE_TIMEOUT - \ + REKEY_TIMEOUT \ + (on receive))" + ); + handshake_initiation_required = true; + } + } + + // If we have sent a packet to a given peer but have not received a + // packet after from that peer for (KEEPALIVE + REKEY_TIMEOUT) ms, + // we initiate a new handshake. + if data_packet_sent > aut_packet_received + && now - aut_packet_received >= KEEPALIVE_TIMEOUT + REKEY_TIMEOUT + && mem::replace(&mut self.timers.want_handshake, false) + { + tracing::warn!("HANDSHAKE(KEEPALIVE + REKEY_TIMEOUT)"); + handshake_initiation_required = true; + } + + if !handshake_initiation_required { + // If a packet has been received from a given peer, but we have not sent one + // back to the given peer in KEEPALIVE ms, we send an empty + // packet. + if data_packet_received > aut_packet_sent + && now - aut_packet_sent >= KEEPALIVE_TIMEOUT + && mem::replace(&mut self.timers.want_keepalive, false) + { + tracing::debug!("KEEPALIVE(KEEPALIVE_TIMEOUT)"); + keepalive_required = true; + } + + // Persistent KEEPALIVE + if persistent_keepalive > 0 + && (now - self.timers[TimePersistentKeepalive] + >= Duration::from_secs(persistent_keepalive as _)) + { + tracing::debug!("KEEPALIVE(PERSISTENT_KEEPALIVE)"); + self.timer_tick(TimePersistentKeepalive); + keepalive_required = true; + } + } + } + } + + if handshake_initiation_required { + return self.format_handshake_initiation(dst, true) + } + + if keepalive_required { + return self.encapsulate(&[], dst) + } + + TunnResult::Done + } + + pub fn time_since_last_handshake(&self) -> Option { + let current_session = self.current; + if self.sessions[current_session % super::N_SESSIONS].is_some() { + let duration_since_tun_start = Instant::now().duration_since(self.timers.time_started); + let duration_since_session_established = self.timers[TimeSessionEstablished]; + + Some(duration_since_tun_start - duration_since_session_established) + } else { + None + } + } + + pub fn persistent_keepalive(&self) -> Option { + let keepalive = self.timers.persistent_keepalive; + + if keepalive > 0 { + Some(keepalive as u16) + } else { + None + } + } +} diff --git a/burrow/src/wireguard/pcb.rs b/burrow/src/wireguard/pcb.rs new file mode 100755 index 0000000..974d84e --- /dev/null +++ b/burrow/src/wireguard/pcb.rs @@ -0,0 +1,173 @@ +use std::{net::SocketAddr, sync::Arc}; + +use anyhow::{Error, Result}; +use fehler::throws; +use ip_network::IpNetwork; +use rand::random; +use tokio::{net::UdpSocket, sync::RwLock, task::JoinHandle}; +use tun::tokio::TunInterface; + +use super::{ + noise::{TunnResult, Tunnel}, + Peer, +}; +use crate::wireguard::noise::errors::WireGuardError; + +#[derive(Debug)] +pub struct PeerPcb { + pub endpoint: SocketAddr, + pub allowed_ips: Vec, + pub handle: RwLock>>, + socket: RwLock>, + tunnel: RwLock, +} + +impl PeerPcb { + #[throws] + pub fn new(peer: Peer) -> Self { + let tunnel = RwLock::new( + Tunnel::new( + peer.private_key, + peer.public_key, + peer.preshared_key, + None, + 1, + None, + ) + .map_err(|s| anyhow::anyhow!("{}", s))?, + ); + Self { + endpoint: peer.endpoint, + allowed_ips: peer.allowed_ips, + handle: RwLock::new(None), + socket: RwLock::new(None), + tunnel, + } + } + + pub async fn open_if_closed(&self) -> Result<(), Error> { + if self.socket.read().await.is_none() { + let socket = UdpSocket::bind("0.0.0.0:0").await?; + socket.connect(self.endpoint).await?; + self.socket.write().await.replace(socket); + } + Ok(()) + } + + pub async fn run(&self, tun_interface: Arc>>) -> Result<(), Error> { + tracing::debug!("starting read loop for pcb... for {:?}", &self); + let rid: i32 = random(); + let mut buf: [u8; 3000] = [0u8; 3000]; + tracing::debug!("start read loop {}", rid); + loop { + tracing::debug!("{}: waiting for packet", rid); + let guard = self.socket.read().await; + let Some(socket) = guard.as_ref() else { + self.open_if_closed().await?; + continue + }; + let mut res_buf = [0; 1500]; + // tracing::debug!("{} : waiting for readability on {:?}", rid, socket); + let len = match socket.recv(&mut res_buf).await { + Ok(l) => l, + Err(e) => { + log::error!("{}: error reading from socket: {:?}", rid, e); + continue + } + }; + let mut res_dat = &res_buf[..len]; + tracing::debug!("{}: Decapsulating {} bytes", rid, len); + tracing::debug!("{:?}", &res_dat); + loop { + match self + .tunnel + .write() + .await + .decapsulate(None, res_dat, &mut buf[..]) + { + TunnResult::Done => break, + TunnResult::Err(e) => { + tracing::error!(message = "Decapsulate error", error = ?e); + break + } + TunnResult::WriteToNetwork(packet) => { + tracing::debug!("WriteToNetwork: {:?}", packet); + self.open_if_closed().await?; + self.socket + .read() + .await + .as_ref() + .unwrap() + .send(packet) + .await?; + tracing::debug!("WriteToNetwork done"); + res_dat = &[]; + continue + } + TunnResult::WriteToTunnelV4(packet, addr) => { + tracing::debug!("WriteToTunnelV4: {:?}, {:?}", packet, addr); + tun_interface.read().await.as_ref().ok_or(anyhow::anyhow!("tun interface does not exist"))?.send(packet).await?; + break + } + TunnResult::WriteToTunnelV6(packet, addr) => { + tracing::debug!("WriteToTunnelV6: {:?}, {:?}", packet, addr); + tun_interface.read().await.as_ref().ok_or(anyhow::anyhow!("tun interface does not exist"))?.send(packet).await?; + break + } + } + } + } + } + + pub async fn send(&self, src: &[u8]) -> Result<(), Error> { + tracing::debug!("Sending packet: {:?}", src); + let mut dst_buf = [0u8; 3000]; + match self.tunnel.write().await.encapsulate(src, &mut dst_buf[..]) { + TunnResult::Done => { + tracing::debug!("Encapsulate done"); + } + TunnResult::Err(e) => { + tracing::error!(message = "Encapsulate error", error = ?e) + } + TunnResult::WriteToNetwork(packet) => { + self.open_if_closed().await?; + let handle = self.socket.read().await; + let Some(socket) = handle.as_ref() else { + tracing::error!("No socket for peer"); + return Ok(()) + }; + tracing::debug!("Our Encapsulated packet: {:?}", packet); + socket.send(packet).await?; + } + _ => panic!("Unexpected result from encapsulate"), + }; + Ok(()) + } + + pub async fn update_timers(&self, dst: &mut [u8]) -> Result<(), Error> { + match self.tunnel.write().await.update_timers(dst) { + TunnResult::Done => {} + TunnResult::Err(WireGuardError::ConnectionExpired) => {} + TunnResult::Err(e) => { + tracing::error!(message = "Update timers error", error = ?e) + } + TunnResult::WriteToNetwork(packet) => { + tracing::debug!("Sending Packet for timer update: {:?}", packet); + self.open_if_closed().await?; + let handle = self.socket.read().await; + let Some(socket) = handle.as_ref() else { + tracing::error!("No socket for peer"); + return Ok(()) + }; + socket.send(packet).await?; + tracing::debug!("Sent Packet for timer update"); + } + _ => panic!("Unexpected result from update_timers"), + }; + Ok(()) + } + + pub async fn reset_rate_limiter(&self) { + self.tunnel.read().await.reset_rate_limiter(); + } +} diff --git a/burrow/src/wireguard/peer.rs b/burrow/src/wireguard/peer.rs new file mode 100755 index 0000000..131b0d4 --- /dev/null +++ b/burrow/src/wireguard/peer.rs @@ -0,0 +1,22 @@ +use std::{fmt, net::SocketAddr}; + +use ip_network::IpNetwork; +use x25519_dalek::{PublicKey, StaticSecret}; + +pub struct Peer { + pub endpoint: SocketAddr, + pub private_key: StaticSecret, + pub public_key: PublicKey, + pub allowed_ips: Vec, + pub preshared_key: Option<[u8; 32]>, +} + +impl fmt::Debug for Peer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Peer") + .field("endpoint", &self.endpoint) + .field("public_key", &self.public_key) + .field("allowed_ips", &self.allowed_ips) + .finish() + } +} diff --git a/burrow/src/wireguard/snapshots/burrow__wireguard__config__tests__tst_config_toml.snap b/burrow/src/wireguard/snapshots/burrow__wireguard__config__tests__tst_config_toml.snap new file mode 100644 index 0000000..3800647 --- /dev/null +++ b/burrow/src/wireguard/snapshots/burrow__wireguard__config__tests__tst_config_toml.snap @@ -0,0 +1,16 @@ +--- +source: burrow/src/wireguard/config.rs +expression: toml +--- +[[Peer]] +public_key = "8GaFjVO6c4luCHG4ONO+1bFG8tO+Zz5/Gy+Geht1USM=" +preshared_key = "ha7j4BjD49sIzyF9SNlbueK0AMHghlj6+u0G3bzC698=" +allowed_ips = ["8.8.8.8/32", "0.0.0.0/0"] +endpoint = "wg.burrow.rs:51820" + +[interface] +private_key = "OEPVdomeLTxTIBvv3TYsJRge0Hp9NMiY0sIrhT8OWG8=" +address = ["10.13.13.2/24"] +listen_port = 51820 +dns = [] + diff --git a/burrow/tmp/conrd.conf b/burrow/tmp/conrd.conf new file mode 100644 index 0000000..52572d1 --- /dev/null +++ b/burrow/tmp/conrd.conf @@ -0,0 +1,8 @@ +[Interface] +PrivateKey = gAaK0KFGOpxY7geGo59XXDufcxeoSNXXNC12mCQmlVs= +Address = 10.1.11.2/32 +DNS = 10.1.11.1 +[Peer] +PublicKey = Ab6V2mgPHiCXaAZfQrNts8ha8RkEzC49VnmMQfe5Yg4= +AllowedIPs = 10.1.11.1/32,10.1.11.2/32,0.0.0.0/0 +Endpoint = 172.251.163.175:51820 \ No newline at end of file diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md new file mode 100644 index 0000000..764c219 --- /dev/null +++ b/docs/GETTING_STARTED.md @@ -0,0 +1,130 @@ +# Getting Started + +## Dependencies + +Before you can start working on Burrow, you'll need to install some dependencies. They are different for each platform: + +
+ Linux + +1. Install **rustup** using the instructions on the [website](https://rustup.rs/): + +```bash +$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +2. Install **Visual Studio Code** from the [website](https://code.visualstudio.com/#alt-downloads), [Snap Store](https://snapcraft.io/code), or your package manager of choice. +
+ +
+ macOS + +1. Install **rustup** using the instructions on the [website](https://rustup.rs/): + +```bash +$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +2. Download and install **Visual Studio Code** from the [website](https://code.visualstudio.com/#alt-downloads), or by using brew: + +``` +brew install --cask visual-studio-code +``` + +3. Download and Install **Xcode** from the [App Store](https://apps.apple.com/us/app/xcode/id497799835) or the [Apple Developer](https://developer.apple.com/downloads) website. + +4. Make sure the _"current"_ version of Xcode matches the one you are using: + +``` +$ xcode-select -p +``` + +If the output is not the version of Xcode you just installed, run the following command to switch to the new version: + +``` +$ sudo xcode-select -s {PATH_TO_XCODE} +``` + +
+ +
+ Windows + +1. Download **Visual Studio** community edition from the [website](https://visualstudio.microsoft.com/vs/). Install the components for "Desktop Development with C++" + +2. Install [**Visual Studio Code**](https://apps.microsoft.com/store/detail/visual-studio-code/XP9KHM4BK9FZ7Q), [**PowerShell**](https://apps.microsoft.com/store/detail/powershell/9MZ1SNWT0N5D) and [**Windows Terminal**](https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701) from the Microsoft Store + +3. Open Windows Terminal and use [winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/) to install **git**, **LLVM** and **rustup**: + +```posh +winget install Git.Git +winget install LLVM.LLVM +winget install Rustlang.Rustup +``` + +4. Install Rust using rustup: + +```posh +rustup toolchain install stable-msvc +``` + +
+ +## Building + +1. Clone the repository: + +``` +git clone git@github.com:hackclub/burrow.git +``` + +2. Open the `burrow` folder in Visual Studio Code: + +``` +code burrow +``` + +3. Install the Visual Studio Code extensions [**rust-analyzer**](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) and [**CodeLLDB**](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb). You should get a suggestion to do this automatically: + + + +4. Compile burrow in Visual Studio Code by selecting `Terminal -> Run Build Task` + +## Running + +
+ Command Line + +You can run burrow on the command line with cargo: + +``` +cargo run +``` + +Cargo will ask for your password because burrow needs permission in order to create a tunnel. + +
+ +
+ Visual Studio Code + +You can debug the Rust program inside of Visual Studio using the **Run and Debug** tab. + +**_This does not work fully yet_**. Visual Studio Code does not have a way to debug programs with administrative privileges. + +
+ +
+ iOS or macOS + +You can run the Burrow app on iOS or macOS using **Xcode**. + +You will need to be logged in with your Apple ID, and it should be a part of **The Hack Foundation** team: + + + +If your Apple ID is not a part of The Hack Foundation team, ask the Slack channel for assistance. + +You should now be able to run the app by opening `Apple/Burrow.xcodeproj` in Xcode, selecting the **App** scheme and clicking **Run**. + +
diff --git a/docs/GTK_APP.md b/docs/GTK_APP.md new file mode 100644 index 0000000..ef73d2b --- /dev/null +++ b/docs/GTK_APP.md @@ -0,0 +1,170 @@ +# Linux GTK App Getting Started + +Currently, the GTK App can be built as a binary or as an AppImage. +Note that the flatpak version can compile but will not run properly! + +## Dependencies + +### Install Build Dependencies + +
+ Debian + + > Note: Burrow currently cannot compile on Debian Stable (Bookworm) due to its outdated dependencies + + 1. Install build dependencies + + ``` + sudo apt install -y clang meson cmake pkg-config libgtk-4-dev libadwaita-1-dev gettext desktop-file-utils + ``` + + 2. Install flatpak builder (Optional) + + ``` + sudo apt install -y flatpak-builder + ``` + + 3. Install AppImage build tools (Optional) + + ``` + sudo apt install -y wget fuse file + ``` + +
+ +
+ Fedora + + 1. Install build dependencies + + ``` + sudo dnf install -y clang ninja-build cmake meson gtk4-devel glib2-devel libadwaita-devel desktop-file-utils libappstream-glib + ``` + + 2. Install flatpak builder (Optional) + + ``` + sudo dnf install -y flatpak-builder + ``` + + 3. Install AppImage build tools (Optional) + + ``` + sudo dnf install -y util-linux wget fuse fuse-libs file + ``` + +
+ +
+ Void Linux (glibc) + + 1. Install build dependencies + + ``` + sudo xbps-install -Sy gcc clang meson cmake pkg-config gtk4-devel gettext desktop-file-utils gtk4-update-icon-cache appstream-glib + ``` + + 2. Install flatpak builder (Optional) + + ``` + sudo xbps-install -Sy flatpak-builder + ``` + + 3. Install AppImage build tools (Optional) + + ``` + sudo xbps-install -Sy wget fuse file + ``` + +
+ +### Flatpak Build Dependencies (Optional) + +``` +flatpak install --user \ + org.gnome.Platform/x86_64/45 \ + org.freedesktop.Sdk.Extension.rust-stable/x86_64/23.08 +``` + +## Building + +
+ General + + 1. Enter the `burrow-gtk` + + ```bash + cd burrow-gtk + ``` + + 2. Perform the meson build + ``` + meson setup build + meson compile -C build + ``` + +
+ +
+ Flatpak + + 1. Compile and install the flatpak + + ``` + flatpak-builder + --user --install --force-clean --disable-rofiles-fuse \ + flatpak_debug/ \ + burrow-gtk/build-aux/com.hackclub.burrow.devel.json + ``` + +
+ +
+ AppImage + + 1. Enter the `burrow-gtk` + + ```bash + cd burrow-gtk + ``` + + 2. Compile the AppImage + + ``` + ./build-aux/build_appimage.sh + ``` + +
+ + +## Running + +
+ General + + The compiled binary can be found in `build/src/burrow-gtk`. + + ``` + ./build/src/burrow-gtk + ``` +
+ +
+ Flatpak + + ``` + flatpak run com.hackclub.burrow-devel + ``` + +
+ +
+ AppImage + + The compiled binary can be found in `build-appimage/Burrow-*.AppImage`. + + ``` + ./build-appimage/Burrow-*.AppImage + ``` + +
diff --git a/docs/extensions.png b/docs/extensions.png new file mode 100644 index 0000000..ad94ee4 Binary files /dev/null and b/docs/extensions.png differ diff --git a/docs/xcode.png b/docs/xcode.png new file mode 100644 index 0000000..469ada3 Binary files /dev/null and b/docs/xcode.png differ diff --git a/package/rpm/post_install b/package/rpm/post_install new file mode 100644 index 0000000..751c190 --- /dev/null +++ b/package/rpm/post_install @@ -0,0 +1,2 @@ +systemctl daemon-reload +systemctl enable burrow diff --git a/package/rpm/pre_uninstall b/package/rpm/pre_uninstall new file mode 100644 index 0000000..e0fef26 --- /dev/null +++ b/package/rpm/pre_uninstall @@ -0,0 +1,3 @@ +systemctl disable burrow.service > /dev/null 2>&1 +systemctl stop burrow.service > /dev/null 2>&1 +systemctl daemon-reload diff --git a/proto/burrow.proto b/proto/burrow.proto new file mode 100644 index 0000000..2355b8d --- /dev/null +++ b/proto/burrow.proto @@ -0,0 +1,72 @@ +syntax = "proto3"; +package burrow; + +import "google/protobuf/timestamp.proto"; + +service Tunnel { + rpc TunnelConfiguration (Empty) returns (stream TunnelConfigurationResponse); + rpc TunnelStart (Empty) returns (Empty); + rpc TunnelStop (Empty) returns (Empty); + rpc TunnelStatus (Empty) returns (stream TunnelStatusResponse); +} + +service Networks { + rpc NetworkAdd (Network) returns (Empty); + rpc NetworkList (Empty) returns (stream NetworkListResponse); + rpc NetworkReorder (NetworkReorderRequest) returns (Empty); + rpc NetworkDelete (NetworkDeleteRequest) returns (Empty); +} + +message NetworkReorderRequest { + int32 id = 1; + int32 index = 2; +} + +message WireGuardPeer { + string endpoint = 1; + repeated string subnet = 2; +} + +message WireGuardNetwork { + string address = 1; + string dns = 2; + repeated WireGuardPeer peer = 3; +} + +message NetworkDeleteRequest { + int32 id = 1; +} + +message Network { + int32 id = 1; + NetworkType type = 2; + bytes payload = 3; +} + +enum NetworkType { + WireGuard = 0; + HackClub = 1; +} + +message NetworkListResponse { + repeated Network network = 1; +} + +message Empty { + +} + +enum State { + Stopped = 0; + Running = 1; +} + +message TunnelStatusResponse { + State state = 1; + optional google.protobuf.Timestamp start = 2; +} + +message TunnelConfigurationResponse { + repeated string addresses = 1; + int32 mtu = 2; +} diff --git a/server_patch.txt b/server_patch.txt new file mode 100644 index 0000000..de8e14c --- /dev/null +++ b/server_patch.txt @@ -0,0 +1,21 @@ +# Add this to ~/server/wg0.conf upon regeneration + +PostUp = iptables -A FORWARD -i %i -j ACCEPT + +PostUp = iptables -A FORWARD -o %i -j ACCEPT + +PostUp = iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE + +PostUp = ip6tables -A FORWARD -i %i -j ACCEPT + +PostUp = ip6tables -A FORWARD -o %i -j ACCEPT + +PostDown = iptables -D FORWARD -i %i -j ACCEPT + +PostDown = iptables -D FORWARD -o %i -j ACCEPT + +PostDown = iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE + +PostDown = ip6tables -D FORWARD -i %i -j ACCEPT + +PostDown = ip6tables -D FORWARD -o %i -j ACCEPT \ No newline at end of file diff --git a/site/.eslintrc.json b/site/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/site/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..71b863e --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/out/ + +/.next/ +next-env.d.ts diff --git a/site/.prettierignore b/site/.prettierignore new file mode 100644 index 0000000..fcac576 --- /dev/null +++ b/site/.prettierignore @@ -0,0 +1,6 @@ +# Ignore artifacts: +build +coverage + +# Ignore all HTML files: +**/*.html \ No newline at end of file diff --git a/site/assets/Bold.woff2 b/site/assets/Bold.woff2 new file mode 100644 index 0000000..8c00084 Binary files /dev/null and b/site/assets/Bold.woff2 differ diff --git a/site/assets/Italic.woff2 b/site/assets/Italic.woff2 new file mode 100644 index 0000000..056909c Binary files /dev/null and b/site/assets/Italic.woff2 differ diff --git a/site/assets/Regular.woff2 b/site/assets/Regular.woff2 new file mode 100644 index 0000000..d7c3d52 Binary files /dev/null and b/site/assets/Regular.woff2 differ diff --git a/site/bun.lockb b/site/bun.lockb new file mode 100755 index 0000000..ea2d137 Binary files /dev/null and b/site/bun.lockb differ diff --git a/site/layout/layout.tsx b/site/layout/layout.tsx new file mode 100644 index 0000000..28ff24d --- /dev/null +++ b/site/layout/layout.tsx @@ -0,0 +1,47 @@ +import { Space_Mono, Poppins } from "next/font/google"; +import localFont from "next/font/local"; + +const space_mono = Space_Mono({ + weight: ["400", "700"], + subsets: ["latin"], + display: "swap", + variable: "--font-space-mono", +}); + +const poppins = Poppins({ + weight: ["400", "500", "600", "700", "800", "900"], + subsets: ["latin"], + display: "swap", + variable: "--font-poppins", +}); + +const phantomSans = localFont({ + src: [ + { + path: "../assets/Regular.woff2", + weight: "400", + style: "normal", + }, + { + path: "../assets/Italic.woff2", + weight: "400", + style: "italic", + }, + { + path: "../assets/Bold.woff2", + weight: "700", + style: "normal", + }, + ], + variable: "--font-phantom-sans", +}); + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} diff --git a/site/next.config.js b/site/next.config.js new file mode 100644 index 0000000..afbb70f --- /dev/null +++ b/site/next.config.js @@ -0,0 +1,13 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + headers() { + return [ + { + source: "/.well-known/apple-app-site-association", + headers: [{ key: "Content-Type", value: "application/json" }], + } + ]; + } +}; + +module.exports = nextConfig; diff --git a/site/package.json b/site/package.json new file mode 100644 index 0000000..4fcacc8 --- /dev/null +++ b/site/package.json @@ -0,0 +1,36 @@ +{ + "name": "burrow", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.4.2", + "@fortawesome/fontawesome-svg-core": "^6.4.2", + "@fortawesome/free-brands-svg-icons": "^6.4.2", + "@fortawesome/free-solid-svg-icons": "^6.4.2", + "@fortawesome/react-fontawesome": "^0.2.0", + "@headlessui/react": "^1.7.17", + "@headlessui/tailwindcss": "^0.2.0", + "@types/node": "20.5.8", + "@types/react": "18.2.21", + "@types/react-dom": "18.2.7", + "autoprefixer": "10.4.15", + "eslint": "8.48.0", + "eslint-config-next": "13.4.19", + "next": "13.4.19", + "postcss": "8.4.29", + "react": "18.2.0", + "react-dom": "18.2.0", + "tailwindcss": "3.3.3", + "typescript": "5.2.2" + }, + "devDependencies": { + "prettier": "^3.0.3", + "prettier-plugin-tailwindcss": "^0.5.4" + } +} diff --git a/site/pages/_app.tsx b/site/pages/_app.tsx new file mode 100644 index 0000000..c0572f6 --- /dev/null +++ b/site/pages/_app.tsx @@ -0,0 +1,14 @@ +import Layout from "@/layout/layout"; +import type { AppProps } from "next/app"; +import { config } from "@fortawesome/fontawesome-svg-core"; +import "@fortawesome/fontawesome-svg-core/styles.css"; +config.autoAddCss = false; +import "static/globals.css"; + +export default function App({ Component, pageProps }: AppProps) { + return ( + + + + ); +} diff --git a/site/pages/_document.tsx b/site/pages/_document.tsx new file mode 100644 index 0000000..ce4b5e1 --- /dev/null +++ b/site/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from "next/document"; + +export default function Document() { + return ( + + + +
+ + + + ); +} diff --git a/site/pages/index.tsx b/site/pages/index.tsx new file mode 100644 index 0000000..73fbc33 --- /dev/null +++ b/site/pages/index.tsx @@ -0,0 +1,154 @@ +import { faGithub } from "@fortawesome/free-brands-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import Head from "next/head"; +import { + faChevronDown, + faChevronUp, + faUpRightFromSquare, +} from "@fortawesome/free-solid-svg-icons"; +import { Menu, Transition } from "@headlessui/react"; +import { useState, useRef, useEffect } from "react"; +export default function Page() { + const [chevron, setChevron] = useState(false); + const menuButtonRef = useRef(null); + const toggleDropdown = () => { + setChevron(!chevron); + }; + const handleClickOutside = (event: MouseEvent) => { + if ( + menuButtonRef.current && + !menuButtonRef.current.contains(event.target as Node) + ) { + setChevron(false); + } + }; + useEffect(() => { + document.addEventListener("click", handleClickOutside); + + return () => { + document.removeEventListener("click", handleClickOutside); + }; + }, []); + return ( + <> + + Burrow + + + +
+
+

+ Burrow Through{" "} + Firewalls +

+
+

+ Burrow is an open source tool for burrowing through firewalls, + built by teenagers at{" "} + + + Hack Club. + + {" "} + + burrow + {" "} + is a Rust-based VPN for getting around restrictive Internet + censors. +

+
+
+
+ +
+ toggleDropdown()} + ref={menuButtonRef} + className="w-50 h-12 rounded-2xl bg-hackClubRed px-3 font-SpaceMono hover:scale-105 md:h-12 md:w-auto md:rounded-3xl md:text-xl 2xl:h-16 2xl:text-2xl " + > + Install for Linux + {chevron ? ( + + ) : ( + + )} + +
+ + +
+ + {({ active }) => ( + + Install for Windows + + )} + + + + Install for MacOS + + +
+
+
+
+ + + +
+ +
+ {/* Footer */} + {/* */} +
+
+ + ); +} diff --git a/site/postcss.config.js b/site/postcss.config.js new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/site/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/site/prettier.config.js b/site/prettier.config.js new file mode 100644 index 0000000..d573118 --- /dev/null +++ b/site/prettier.config.js @@ -0,0 +1,3 @@ +module.exports = { + plugins: ["prettier-plugin-tailwindcss"], +}; diff --git a/site/public/.well-known/apple-app-site-association b/site/public/.well-known/apple-app-site-association new file mode 100644 index 0000000..63262fb --- /dev/null +++ b/site/public/.well-known/apple-app-site-association @@ -0,0 +1,21 @@ +{ + "applinks": { + "details": [ + { + "appIDs": [ + "P6PV2R9443.com.hackclub.burrow" + ], + "components": [ + { + "/": "/callback/*" + } + ] + } + ] + }, + "webcredentials": { + "apps": [ + "P6PV2R9443.com.hackclub.burrow" + ] + } +} diff --git a/site/public/hackclub.svg b/site/public/hackclub.svg new file mode 100644 index 0000000..38c2a68 --- /dev/null +++ b/site/public/hackclub.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/static/globals.css b/site/static/globals.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/site/static/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/site/tailwind.config.ts b/site/tailwind.config.ts new file mode 100644 index 0000000..3df6f5a --- /dev/null +++ b/site/tailwind.config.ts @@ -0,0 +1,28 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + backgroundBlack: "#17171D", + hackClubRed: "#EC3750", + hackClubBlueShade: "#32323D", + hackClubBlue: "#338EDA", + burrowStroke: "#595959", + burrowHover: "#3D3D3D", + }, + fontFamily: { + SpaceMono: ["var(--font-space-mono)"], + Poppins: ["var(--font-poppins)"], + PhantomSans: ["var(--font-phantom-sans)"], + }, + }, + }, + plugins: [require("@headlessui/tailwindcss")({ prefix: "ui" })], +}; +export default config; diff --git a/site/tsconfig.json b/site/tsconfig.json new file mode 100644 index 0000000..c714696 --- /dev/null +++ b/site/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/systemd/burrow.service b/systemd/burrow.service new file mode 100644 index 0000000..8d35355 --- /dev/null +++ b/systemd/burrow.service @@ -0,0 +1,9 @@ +[Unit] +Description=Burrow +After=burrow.socket + +[Service] +ExecStart=/usr/bin/burrow daemon + +[Install] +WantedBy=multi-user.target diff --git a/systemd/burrow.socket b/systemd/burrow.socket new file mode 100644 index 0000000..c5da49d --- /dev/null +++ b/systemd/burrow.socket @@ -0,0 +1,8 @@ +[Unit] +Description=Burrow Socket + +[Socket] +ListenStream=/run/burrow.sock + +[Install] +WantedBy=sockets.target diff --git a/tun-async/Cargo.toml b/tun-async/Cargo.toml deleted file mode 100644 index e011ca4..0000000 --- a/tun-async/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "tun-async" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -tun = { version = "0.1", path = "../tun" } diff --git a/tun-async/src/lib.rs b/tun-async/src/lib.rs deleted file mode 100644 index 7d12d9a..0000000 --- a/tun-async/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/tun/Cargo.toml b/tun/Cargo.toml index 9a6f8fd..1b07833 100644 --- a/tun/Cargo.toml +++ b/tun/Cargo.toml @@ -5,20 +5,36 @@ edition = "2021" [dependencies] libc = "0.2" -nix = { version = "0.25", features = ["ioctl"] } -socket2 = "0.4" -tokio = { version = "1.21", features = [] } +fehler = "1.0" +nix = { version = "0.26", features = ["ioctl"] } +socket2 = "0.5" +tokio = { version = "1.37", default-features = false, optional = true } +byteorder = "1.4" +tracing = "0.1" +log = "0.4" +serde = { version = "1", features = ["derive"], optional = true } +schemars = { version = "0.8", optional = true } + +futures = { version = "0.3.28", optional = true } + +[features] +serde = ["dep:serde", "dep:schemars"] +tokio = ["tokio/net", "dep:tokio", "dep:futures"] [target.'cfg(windows)'.dependencies] +lazy_static = "1.4" libloading = "0.7" +tempfile = "3.5" widestring = "1.0" +windows = { version = "0.48", features = [ + "Win32_Foundation", + "Win32_NetworkManagement_IpHelper", +] } [target.'cfg(windows)'.build-dependencies] anyhow = "1.0" -bindgen = "0.61" -hex-literal = "0.3" -platforms = "3.0" -reqwest = { version = "0.11", features = ["native-tls"] } -sha2 = "0.10" -tokio = { version = "1.21", features = ["rt"] } +bindgen = "0.65" +reqwest = { version = "0.11" } +ssri = { version = "9.0", default-features = false } +tokio = { version = "1.28", features = ["rt", "macros"] } zip = { version = "0.6", features = ["deflate"] } diff --git a/tun/build.rs b/tun/build.rs index 6ad98c3..8da8a40 100644 --- a/tun/build.rs +++ b/tun/build.rs @@ -1,40 +1,98 @@ +#[cfg(not(windows))] +fn main() { + println!("cargo:rerun-if-changed=build.rs"); +} + #[cfg(windows)] #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { - use std::io::{Cursor, Read}; + let out_dir = std::env::var("OUT_DIR")?; + let path = std::path::PathBuf::from(out_dir); + generate(&path).await?; - let buf = reqwest::get("https://www.wintun.net/builds/wintun-0.14.1.zip") - .await? - .bytes() - .await?; - assert_content_hash( - &buf, - hex_literal::hex!("07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51"), - ); - let mut archive = zip::ZipArchive::new(Cursor::new(buf))?; + println!("cargo:rerun-if-changed=build.rs"); + Ok(()) +} - let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?); +#[cfg(windows)] +async fn generate(out_dir: &std::path::Path) -> anyhow::Result<()> { + use std::{fs::File, io::Write}; + + use anyhow::Context; + + let bindings_path = out_dir.join("wintun.rs"); + let binary_path = out_dir.join("wintun.dll"); + println!("cargo:rerun-if-changed={}", bindings_path.to_str().unwrap()); + println!("cargo:rerun-if-changed={}", binary_path.to_str().unwrap()); + + if let (Ok(..), Ok(..)) = (File::open(&bindings_path), File::open(&binary_path)) { + return Ok(()) + }; + + let archive = download(out_dir) + .await + .context("Failed to download wintun")?; + + let (bindings, binary) = parse(archive).context("Failed to parse wintun archive")?; + + bindings + .write_to_file(bindings_path) + .context("Failed to write bindings")?; + + let mut file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .open(binary_path) + .context("Failed to write binary")?; + file.write_all(&binary)?; + + Ok(()) +} + +#[cfg(windows)] +async fn download(directory: &std::path::Path) -> anyhow::Result { + use std::{io::Write, str::FromStr}; + + let path = directory.join(WINTUN_FILENAME); + let mut file = match std::fs::OpenOptions::new().read(true).open(&path) { + Ok(existing) => return Ok(existing), + Err(_e) => std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path)?, + }; + + let mut url = reqwest::Url::from_str("https://www.wintun.net/builds")?; + url.path_segments_mut().unwrap().push(WINTUN_FILENAME); + + let body = reqwest::get(url).await?.bytes().await?; + + ssri::IntegrityChecker::new(WINTUN_INTEGRITY.parse()?) + .chain(&body) + .result()?; + + file.set_len(0)?; + file.write_all(&body)?; + + Ok(file) +} + +#[cfg(windows)] +fn parse(file: std::fs::File) -> anyhow::Result<(bindgen::Bindings, Vec)> { + use std::io::Read; + + use anyhow::Context; + + let reader = std::io::BufReader::new(file); + let mut archive = zip::ZipArchive::new(reader)?; let mut header = String::new(); archive .by_name("wintun/include/wintun.h")? .read_to_string(&mut header)?; - header.push_str( - "WINTUN_CLOSE_ADAPTER_FUNC WintunCloseAdapter; - WINTUN_OPEN_ADAPTER_FUNC WintunOpenAdapter; - WINTUN_GET_ADAPTER_LUID_FUNC WintunGetAdapterLUID; - WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC WintunGetRunningDriverVersion; - WINTUN_DELETE_DRIVER_FUNC WintunDeleteDriver; - WINTUN_SET_LOGGER_FUNC WintunSetLogger; - WINTUN_START_SESSION_FUNC WintunStartSession; - WINTUN_END_SESSION_FUNC WintunEndSession; - WINTUN_CREATE_ADAPTER_FUNC WintunCreateAdapter; - WINTUN_GET_READ_WAIT_EVENT_FUNC WintunGetReadWaitEvent; - WINTUN_RECEIVE_PACKET_FUNC WintunReceivePacket; - WINTUN_RELEASE_RECEIVE_PACKET_FUNC WintunReleaseReceivePacket; - WINTUN_ALLOCATE_SEND_PACKET_FUNC WintunAllocateSendPacket; - WINTUN_SEND_PACKET_FUNC WintunSendPacket;", - ); + header.push_str(WINTUN_BINDINGS_PREAMBLE); + let bindings = bindgen::Builder::default() .header_contents("wintun.h", &header) .allowlist_function("Wintun.*") @@ -42,38 +100,47 @@ async fn main() -> anyhow::Result<()> { .dynamic_library_name("wintun") .dynamic_link_require_all(true) .generate() + .context("Failed to generate bindings from wintun archive") .unwrap(); - bindings.write_to_file(out_dir.join("wintun.rs"))?; - let mut library = Vec::new(); - let platform = platforms::Platform::find(&std::env::var("TARGET")?).unwrap(); - let arch = match platform.target_arch { - platforms::target::Arch::Arm => "arm", - platforms::Arch::AArch64 => "arm64", - platforms::Arch::X86 => "x86", - platforms::Arch::X86_64 => "amd64", - arch => panic!("{} is not a supported architecture", arch), + let mut binary = Vec::new(); + let target = std::env::var("TARGET")?; + let arch = match target.split('-').next() { + Some("i686") => "x86", + Some("x86_64") => "amd64", + Some("aarch64") => "arm64", + Some("thumbv7a") => "arm", + Some(a) => panic!("{} is not a supported architecture", a), + None => unreachable!(), }; archive .by_name(&format!("wintun/bin/{}/wintun.dll", arch))? - .read_to_end(&mut library)?; - std::fs::write(out_dir.join("wintun.dll"), library)?; + .read_to_end(&mut binary) + .context("Failed to read binary from wintun archive")?; - println!("cargo:rerun-if-changed=build.rs"); - - Ok(()) -} - -#[cfg(not(windows))] -fn main() { - println!("cargo:rerun-if-changed=build.rs"); + Ok((bindings, binary)) } #[cfg(windows)] -fn assert_content_hash(content: &[u8], hash: [u8; 32]) { - use sha2::digest::Update; - use sha2::Digest; +const WINTUN_FILENAME: &str = "wintun-0.14.1.zip"; - let computed = sha2::Sha256::new().chain(content).finalize(); - assert_eq!(computed.as_slice(), &hash[..]); -} +#[cfg(windows)] +const WINTUN_INTEGRITY: &str = "sha256-B8JWGF1u42UuCfpVwLZz4mJLVl4CxLkJHHnKfS8k71E="; + +#[cfg(windows)] +const WINTUN_BINDINGS_PREAMBLE: &str = r#" +WINTUN_CLOSE_ADAPTER_FUNC WintunCloseAdapter; +WINTUN_OPEN_ADAPTER_FUNC WintunOpenAdapter; +WINTUN_GET_ADAPTER_LUID_FUNC WintunGetAdapterLUID; +WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC WintunGetRunningDriverVersion; +WINTUN_DELETE_DRIVER_FUNC WintunDeleteDriver; +WINTUN_SET_LOGGER_FUNC WintunSetLogger; +WINTUN_START_SESSION_FUNC WintunStartSession; +WINTUN_END_SESSION_FUNC WintunEndSession; +WINTUN_CREATE_ADAPTER_FUNC WintunCreateAdapter; +WINTUN_GET_READ_WAIT_EVENT_FUNC WintunGetReadWaitEvent; +WINTUN_RECEIVE_PACKET_FUNC WintunReceivePacket; +WINTUN_RELEASE_RECEIVE_PACKET_FUNC WintunReleaseReceivePacket; +WINTUN_ALLOCATE_SEND_PACKET_FUNC WintunAllocateSendPacket; +WINTUN_SEND_PACKET_FUNC WintunSendPacket; +"#; diff --git a/tun/src/apple/kern_control.rs b/tun/src/apple/kern_control.rs deleted file mode 100644 index f913fb6..0000000 --- a/tun/src/apple/kern_control.rs +++ /dev/null @@ -1,53 +0,0 @@ -use libc::{sockaddr_ctl, AF_SYSTEM, AF_SYS_CONTROL}; -use std::io::Result; -use std::mem::size_of; -use std::os::unix::io::AsRawFd; - -/// Trait to connect to kernel extensions on Apple platforms -/// -/// Pulled from XNU source: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/kern_control.h -pub trait SysControlSocket { - fn resolve(&self, name: &str, index: u32) -> Result; -} - -impl SysControlSocket for socket2::Socket { - fn resolve(&self, name: &str, index: u32) -> Result { - let mut info = sys::ctl_info { - ctl_id: 0, - ctl_name: [0; 96], - }; - info.ctl_name[..name.len()].copy_from_slice(name.as_bytes()); - - unsafe { sys::resolve_ctl_info(self.as_raw_fd(), &mut info as *mut sys::ctl_info)? }; - - let (_, addr) = unsafe { - socket2::SockAddr::init(|addr_storage, len| { - *len = size_of::() as u32; - - let mut addr: &mut sockaddr_ctl = &mut *addr_storage.cast(); - addr.sc_len = *len as u8; - addr.sc_family = AF_SYSTEM as u8; - addr.ss_sysaddr = AF_SYS_CONTROL as u16; - addr.sc_id = info.ctl_id; - addr.sc_unit = index; - Ok(()) - }) - }?; - - Ok(addr) - } -} - -mod sys { - use nix::ioctl_readwrite; - - const MAX_KCTL_NAME: usize = 96; - - #[repr(C)] - pub struct ctl_info { - pub ctl_id: u32, - pub ctl_name: [u8; MAX_KCTL_NAME], - } - - ioctl_readwrite!(resolve_ctl_info, b'N', 3, ctl_info); -} diff --git a/tun/src/apple/mod.rs b/tun/src/apple/mod.rs deleted file mode 100644 index cce4ea1..0000000 --- a/tun/src/apple/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -use socket2::SockAddr; -use std::io::Result; -use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; - -mod kern_control; -mod queue; - -pub use queue::TunQueue; - -use crate::syscall; -use crate::unix::copy_if_name; -use kern_control::SysControlSocket; - -pub struct TunInterface { - socket: socket2::Socket, -} - -impl TunInterface { - pub fn new() -> Result { - TunInterface::connect(None) - } - - fn connect(addr: Option) -> Result { - use socket2::{Domain, Protocol, Socket, Type}; - - let socket = Socket::new( - Domain::from(libc::AF_SYSTEM), - Type::DGRAM, - Some(Protocol::from(libc::SYSPROTO_CONTROL)), - )?; - let addr = match addr { - Some(addr) => addr, - None => socket.resolve(sys::UTUN_CONTROL_NAME, 0)?, - }; - socket.connect(&addr)?; - - Ok(TunInterface { socket }) - } - - pub fn name(&self) -> Result { - let mut buf = [0i8; libc::IFNAMSIZ]; - let mut len = buf.len() as libc::socklen_t; - syscall!(getsockopt( - self.as_raw_fd(), - libc::SYSPROTO_CONTROL, - sys::UTUN_OPT_IFNAME, - buf.as_mut_ptr() as *mut libc::c_void, - &mut len, - ))?; - let name = copy_if_name(buf); - Ok(name) - } - - pub fn queue(&self) -> Result { - todo!() - } -} - -impl AsRawFd for TunInterface { - fn as_raw_fd(&self) -> RawFd { - self.socket.as_raw_fd() - } -} - -impl IntoRawFd for TunInterface { - fn into_raw_fd(self) -> RawFd { - self.socket.into_raw_fd() - } -} - -mod sys { - pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control"; - - pub const UTUN_OPT_IFNAME: libc::c_int = 2; - - /// Copied from https://github.com/rust-lang/socket2/blob/61314a231f73964b3db969ef72c0e9479df320f3/src/sys/unix.rs#L168-L178 - /// getsockopt is not exposed by socket2 - #[macro_export] - macro_rules! syscall { - ($fn: ident ( $($arg: expr),* $(,)* ) ) => {{ - #[allow(unused_unsafe)] - let res = unsafe { libc::$fn($($arg, )*) }; - if res == -1 { - Err(std::io::Error::last_os_error()) - } else { - Ok(res) - } - }}; - } -} diff --git a/tun/src/apple/queue.rs b/tun/src/apple/queue.rs deleted file mode 100644 index fdc23b6..0000000 --- a/tun/src/apple/queue.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; - -pub struct TunQueue { - socket: socket2::Socket, -} - -impl AsRawFd for TunQueue { - fn as_raw_fd(&self) -> RawFd { - self.socket.as_raw_fd() - } -} - -impl IntoRawFd for TunQueue { - fn into_raw_fd(self) -> RawFd { - self.socket.into_raw_fd() - } -} diff --git a/tun/src/lib.rs b/tun/src/lib.rs index 7082e2d..a1ca636 100644 --- a/tun/src/lib.rs +++ b/tun/src/lib.rs @@ -1,16 +1,18 @@ -#[cfg(target_vendor = "apple")] -#[path = "apple/mod.rs"] -mod imp; - -#[cfg(target_os = "linux")] -#[path = "linux.rs"] -mod imp; +#![deny(missing_debug_implementations)] #[cfg(target_os = "windows")] #[path = "windows/mod.rs"] -mod imp; +mod os_imp; #[cfg(any(target_os = "linux", target_vendor = "apple"))] -pub(crate) mod unix; +#[path = "unix/mod.rs"] +pub(crate) mod os_imp; -pub use imp::{TunInterface, TunQueue}; +mod options; + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[cfg(feature = "tokio")] +pub mod tokio; + +pub use options::TunOptions; +pub use os_imp::{TunInterface, TunQueue}; diff --git a/tun/src/linux.rs b/tun/src/linux.rs deleted file mode 100644 index b1ef0ae..0000000 --- a/tun/src/linux.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::fs::OpenOptions; -use std::io::Result; -use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; - -use crate::unix::copy_if_name; - -pub struct TunInterface { - inner: socket2::Socket, -} - -impl TunInterface { - pub fn new() -> Result { - let file = OpenOptions::new() - .read(true) - .write(true) - .open("/dev/net/tun")?; - - let iff = libc::ifreq { - ifr_name: [0; libc::IFNAMSIZ], - ifr_ifru: libc::__c_anonymous_ifr_ifru { - ifru_flags: (libc::IFF_TUN | libc::IFF_TUN_EXCL | libc::IFF_NO_PI) as i16, - }, - }; - unsafe { sys::tun_set_iff(file.as_raw_fd(), &iff)? }; - - let inner = unsafe { socket2::Socket::from_raw_fd(file.into_raw_fd()) }; - Ok(TunInterface { inner }) - } - - pub fn name(&self) -> Result { - let mut iff = libc::ifreq { - ifr_name: [0; libc::IFNAMSIZ], - ifr_ifru: libc::__c_anonymous_ifr_ifru { ifru_flags: 0 }, - }; - unsafe { sys::tun_get_iff(self.inner.as_raw_fd(), &mut iff)? }; - - let name = copy_if_name(iff.ifr_name); - Ok(name) - } -} - -mod sys { - use nix::{ioctl_read_bad, ioctl_write_ptr_bad, request_code_read, request_code_write}; - use std::mem::size_of; - - ioctl_write_ptr_bad!( - tun_set_iff, - request_code_write!(b'T', 202, size_of::()), - libc::ifreq - ); - ioctl_read_bad!( - tun_get_iff, - request_code_read!(b'T', 210, size_of::()), - libc::ifreq - ); -} - -pub struct TunQueue; diff --git a/tun/src/options.rs b/tun/src/options.rs new file mode 100644 index 0000000..e21bf5f --- /dev/null +++ b/tun/src/options.rs @@ -0,0 +1,59 @@ +use std::io::Error; + +use fehler::throws; + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[cfg(feature = "tokio")] +use super::tokio::TunInterface; + +#[derive(Debug, Clone, Default)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) +)] +pub struct TunOptions { + /// (Windows + Linux) Name the tun interface. + pub name: Option, + /// (Linux) Don't include packet information. + pub no_pi: bool, + /// (Linux) Avoid opening an existing persistant device. + pub tun_excl: bool, + /// (Apple) Retrieve the tun interface + pub tun_retrieve: bool, + /// (Linux) The IP address of the tun interface. + pub address: Vec, +} + +impl TunOptions { + pub fn new() -> Self { + Self::default() + } + + pub fn name(mut self, name: &str) -> Self { + self.name = Some(name.to_owned()); + self + } + + pub fn no_pi(mut self, enable: bool) -> Self { + self.no_pi = enable; + self + } + + pub fn tun_excl(mut self, enable: bool) -> Self { + self.tun_excl = enable; + self + } + + pub fn address(mut self, address: Vec) -> Self { + self.address = address.iter().map(|x| x.to_string()).collect(); + self + } + + #[cfg(any(target_os = "linux", target_vendor = "apple"))] + #[cfg(feature = "tokio")] + #[throws] + pub fn open(self) -> TunInterface { + let ti = super::TunInterface::new_with_options(self)?; + TunInterface::new(ti)? + } +} diff --git a/tun/src/queue.rs b/tun/src/queue.rs deleted file mode 100644 index 8b13789..0000000 --- a/tun/src/queue.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tun/src/tokio/mod.rs b/tun/src/tokio/mod.rs new file mode 100644 index 0000000..bd27109 --- /dev/null +++ b/tun/src/tokio/mod.rs @@ -0,0 +1,41 @@ +use std::io; + +use tokio::io::unix::AsyncFd; +use tracing::instrument; + +#[derive(Debug)] +pub struct TunInterface { + pub inner: AsyncFd, +} + +impl TunInterface { + #[instrument] + pub fn new(mut tun: crate::TunInterface) -> io::Result { + tun.set_nonblocking(true)?; + Ok(Self { inner: AsyncFd::new(tun)? }) + } + + #[instrument] + pub async fn send(&self, buf: &[u8]) -> io::Result { + loop { + let mut guard = self.inner.writable().await?; + match guard.try_io(|inner| inner.get_ref().send(buf)) { + Ok(result) => return result, + Err(_would_block) => continue, + } + } + } + + pub async fn recv(&self, buf: &mut [u8]) -> io::Result { + loop { + let mut guard = self.inner.readable().await?; + match guard.try_io(|inner| inner.get_ref().recv(buf)) { + Ok(result) => return result, + Err(_would_block) => { + tracing::debug!("WouldBlock"); + continue + } + } + } + } +} diff --git a/tun/src/unix.rs b/tun/src/unix.rs deleted file mode 100644 index 17c425c..0000000 --- a/tun/src/unix.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::ffi::{c_char, CStr}; - -pub fn copy_if_name(buf: [c_char; libc::IFNAMSIZ]) -> String { - // TODO: Switch to `CStr::from_bytes_until_nul` when stabilized - unsafe { - CStr::from_ptr(buf.as_ptr() as *const _) - .to_str() - .unwrap() - .to_string() - } -} diff --git a/tun/src/unix/apple/kern_control.rs b/tun/src/unix/apple/kern_control.rs new file mode 100644 index 0000000..6075233 --- /dev/null +++ b/tun/src/unix/apple/kern_control.rs @@ -0,0 +1,39 @@ +use std::{io::Error, mem::size_of, os::unix::io::AsRawFd}; + +use fehler::throws; + +use super::sys; + +/// Trait to connect to kernel extensions on Apple platforms +/// +/// Pulled from XNU source: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/kern_control.h +pub trait SysControlSocket { + #[throws] + fn resolve(&self, name: &str, index: u32) -> socket2::SockAddr; +} + +impl SysControlSocket for socket2::Socket { + #[throws] + fn resolve(&self, name: &str, index: u32) -> socket2::SockAddr { + let mut info = sys::ctl_info { ctl_id: 0, ctl_name: [0; 96] }; + info.ctl_name[..name.len()].copy_from_slice(name.as_bytes()); + + unsafe { sys::resolve_ctl_info(self.as_raw_fd(), &mut info as *mut sys::ctl_info)? }; + + let (_, addr) = unsafe { + socket2::SockAddr::try_init(|addr_storage, len| { + *len = size_of::() as u32; + + let addr: &mut sys::sockaddr_ctl = &mut *addr_storage.cast(); + addr.sc_len = *len as u8; + addr.sc_family = sys::AF_SYSTEM as u8; + addr.ss_sysaddr = sys::AF_SYS_CONTROL as u16; + addr.sc_id = info.ctl_id; + addr.sc_unit = index; + Ok(()) + }) + }?; + + addr + } +} diff --git a/tun/src/unix/apple/mod.rs b/tun/src/unix/apple/mod.rs new file mode 100644 index 0000000..74e93eb --- /dev/null +++ b/tun/src/unix/apple/mod.rs @@ -0,0 +1,252 @@ +use std::{ + io::{Error, IoSlice}, + mem, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4}, + os::fd::{AsRawFd, FromRawFd, RawFd}, +}; + +use byteorder::{ByteOrder, NetworkEndian}; +use fehler::throws; +use libc::{c_char, iovec, writev, AF_INET, AF_INET6}; +use socket2::{Domain, SockAddr, Socket, Type}; +use tracing::{self, instrument}; + +pub mod kern_control; +pub mod sys; + +use kern_control::SysControlSocket; + +use super::{ifname_to_string, string_to_ifname}; +use crate::TunOptions; + +#[derive(Debug)] +pub struct TunInterface { + pub(crate) socket: socket2::Socket, +} + +impl TunInterface { + #[throws] + #[instrument] + pub fn new() -> TunInterface { + Self::new_with_options(TunOptions::new())? + } + + #[throws] + #[instrument] + pub fn new_with_options(options: TunOptions) -> TunInterface { + let ti = if options.tun_retrieve { + TunInterface::retrieve().ok_or(Error::new( + std::io::ErrorKind::NotFound, + "No tun interface found", + ))? + } else { + TunInterface::connect(0)? + }; + ti.configure(options)?; + ti + } + + pub fn retrieve() -> Option { + (3..100) + .filter_map(|fd| unsafe { + let peer_addr = socket2::SockAddr::try_init(|storage, len| { + *len = mem::size_of::() as u32; + libc::getpeername(fd, storage as *mut _, len); + Ok(()) + }) + .map(|(_, addr)| (fd, addr)); + peer_addr.ok() + }) + .filter(|(_fd, addr)| { + let ctl_addr = unsafe { &*(addr.as_ptr() as *const libc::sockaddr_ctl) }; + addr.family() == libc::AF_SYSTEM as u8 + && ctl_addr.ss_sysaddr == libc::AF_SYS_CONTROL as u16 + }) + .map(|(fd, _)| { + let socket = unsafe { socket2::Socket::from_raw_fd(fd) }; + TunInterface { socket } + }) + .next() + } + + #[throws] + fn configure(&self, options: TunOptions) { + for addr in options.address { + if let Ok(addr) = addr.parse::() { + match addr { + IpAddr::V4(addr) => self.set_ipv4_addr(addr)?, + IpAddr::V6(addr) => self.set_ipv6_addr(addr)?, + } + } + } + } + + #[throws] + #[instrument] + fn connect(index: u32) -> TunInterface { + use socket2::{Domain, Protocol, Socket, Type}; + + let socket = Socket::new( + Domain::from(sys::AF_SYSTEM), + Type::DGRAM, + Some(Protocol::from(sys::SYSPROTO_CONTROL)), + )?; + let addr = socket.resolve(sys::UTUN_CONTROL_NAME, index)?; + socket.connect(&addr)?; + + TunInterface { socket } + } + + #[throws] + #[instrument] + pub fn name(&self) -> String { + let mut buf = [0 as c_char; sys::IFNAMSIZ]; + let mut len = buf.len() as sys::socklen_t; + sys::syscall!(getsockopt( + self.as_raw_fd(), + sys::SYSPROTO_CONTROL, + sys::UTUN_OPT_IFNAME, + buf.as_mut_ptr() as *mut sys::c_void, + &mut len, + ))?; + ifname_to_string(buf) + } + + #[throws] + #[instrument] + fn ifreq(&self) -> sys::ifreq { + let mut iff: sys::ifreq = unsafe { mem::zeroed() }; + iff.ifr_name = string_to_ifname(&self.name()?); + iff + } + + #[throws] + #[instrument] + fn in6_ifreq(&self) -> sys::in6_ifreq { + let mut iff: sys::in6_ifreq = unsafe { mem::zeroed() }; + iff.ifr_name = string_to_ifname(&self.name()?); + iff + } + + #[throws] + #[instrument] + pub fn set_ipv4_addr(&self, addr: Ipv4Addr) { + let addr = SockAddr::from(SocketAddrV4::new(addr, 0)); + let mut iff = self.ifreq()?; + iff.ifr_ifru.ifru_addr = unsafe { *addr.as_ptr() }; + self.perform(|fd| unsafe { sys::if_set_addr(fd, &iff) })?; + tracing::info!("ipv4_addr_set: {:?} (fd: {:?})", addr, self.as_raw_fd()) + } + + #[throws] + #[instrument] + pub fn ipv4_addr(&self) -> Ipv4Addr { + let mut iff = self.ifreq()?; + self.perform(|fd| unsafe { sys::if_get_addr(fd, &mut iff) })?; + let addr = unsafe { *(&iff.ifr_ifru.ifru_addr as *const _ as *const sys::sockaddr_in) }; + Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr)) + } + + #[throws] + pub fn set_ipv6_addr(&self, _addr: Ipv6Addr) { + // let addr = SockAddr::from(SocketAddrV6::new(addr, 0, 0, 0)); + // println!("addr: {:?}", addr); + // let mut iff = self.in6_ifreq()?; + // let sto = addr.as_storage(); + // let ifadddr_ptr: *const sockaddr_in6 = addr_of!(sto).cast(); + // iff.ifr_ifru.ifru_addr = unsafe { *ifadddr_ptr }; + // println!("ifru addr set"); + // println!("{:?}", sys::SIOCSIFADDR_IN6); + // self.perform6(|fd| unsafe { sys::if_set_addr6(fd, &iff) })?; + // tracing::info!("ipv6_addr_set"); + tracing::warn!("Setting IPV6 address on MacOS CLI mode is not supported yet."); + } + + #[throws] + fn perform(&self, perform: impl FnOnce(RawFd) -> Result) -> R { + let span = tracing::info_span!("perform", fd = self.as_raw_fd()); + let _enter = span.enter(); + + let socket = Socket::new(Domain::IPV4, Type::DGRAM, None)?; + perform(socket.as_raw_fd())? + } + + #[throws] + fn perform6(&self, perform: impl FnOnce(RawFd) -> Result) -> R { + let span = tracing::info_span!("perform6", fd = self.as_raw_fd()); + let _enter = span.enter(); + + let socket = Socket::new(Domain::IPV6, Type::DGRAM, None)?; + perform(socket.as_raw_fd())? + } + + #[throws] + #[instrument] + pub fn mtu(&self) -> i32 { + let mut iff = self.ifreq()?; + self.perform(|fd| unsafe { sys::if_get_mtu(fd, &mut iff) })?; + let mtu = unsafe { iff.ifr_ifru.ifru_mtu }; + + mtu + } + + #[throws] + #[instrument] + pub fn set_mtu(&self, mtu: i32) { + let mut iff = self.ifreq()?; + iff.ifr_ifru.ifru_mtu = mtu; + self.perform(|fd| unsafe { sys::if_set_mtu(fd, &iff) })?; + tracing::info!("mtu_set: {:?} (fd: {:?})", mtu, self.as_raw_fd()) + } + + #[throws] + #[instrument] + pub fn netmask(&self) -> Ipv4Addr { + let mut iff = self.ifreq()?; + self.perform(|fd| unsafe { sys::if_get_netmask(fd, &mut iff) })?; + + let netmask = + unsafe { *(&iff.ifr_ifru.ifru_netmask as *const _ as *const sys::sockaddr_in) }; + + Ipv4Addr::from(u32::from_be(netmask.sin_addr.s_addr)) + } + + #[throws] + #[instrument] + pub fn set_netmask(&self, addr: Ipv4Addr) { + let addr = SockAddr::from(SocketAddrV4::new(addr, 0)); + let mut iff = self.ifreq()?; + iff.ifr_ifru.ifru_netmask = unsafe { *addr.as_ptr() }; + self.perform(|fd| unsafe { sys::if_set_netmask(fd, &iff) })?; + tracing::info!( + "netmask_set: {:?} (fd: {:?})", + unsafe { iff.ifr_ifru.ifru_netmask }, + self.as_raw_fd() + ) + } + + #[throws] + #[instrument] + pub fn send(&self, buf: &[u8]) -> usize { + use std::io::ErrorKind; + let proto = match buf[0] >> 4 { + 6 => Ok(AF_INET6), + 4 => Ok(AF_INET), + _ => Err(Error::new(ErrorKind::InvalidInput, "Invalid IP version")), + }?; + let mut pbuf = [0; 4]; + NetworkEndian::write_i32(&mut pbuf, proto); + + let bufs = [IoSlice::new(&pbuf), IoSlice::new(buf)]; + let bytes_written: isize = unsafe { + writev( + self.as_raw_fd(), + bufs.as_ptr() as *const iovec, + bufs.len() as i32, + ) + }; + bytes_written + .try_into() + .map_err(|_| Error::new(ErrorKind::Other, "Conversion error"))? + } +} diff --git a/tun/src/unix/apple/sys.rs b/tun/src/unix/apple/sys.rs new file mode 100644 index 0000000..d48d6ee --- /dev/null +++ b/tun/src/unix/apple/sys.rs @@ -0,0 +1,203 @@ +use std::mem; + +use libc::{c_char, c_int, c_short, c_uint, c_ulong, sockaddr, sockaddr_in6, time_t}; +pub use libc::{ + c_void, + sockaddr_ctl, + sockaddr_in, + socklen_t, + AF_SYSTEM, + AF_SYS_CONTROL, + IFNAMSIZ, + SYSPROTO_CONTROL, +}; +use nix::{ + ioctl_read_bad, + ioctl_readwrite, + ioctl_write_ptr_bad, + request_code_readwrite, + request_code_write, +}; + +pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control"; +pub const UTUN_OPT_IFNAME: libc::c_int = 2; + +pub const MAX_KCTL_NAME: usize = 96; +pub const SCOPE6_ID_MAX: usize = 16; + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct ctl_info { + pub ctl_id: u32, + pub ctl_name: [u8; MAX_KCTL_NAME], +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct ifkpi { + pub ifk_module_id: c_uint, + pub ifk_type: c_uint, + pub ifk_ptr: *mut c_void, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct ifdevmtu { + pub ifdm_current: c_int, + pub ifdm_min: c_int, + pub ifdm_max: c_int, +} + +#[repr(C)] +pub union ifr_ifru { + pub ifru_addr: sockaddr, + pub ifru_dstaddr: sockaddr, + pub ifru_broadaddr: sockaddr, + pub ifru_netmask: sockaddr, + pub ifru_flags: c_short, + pub ifru_metric: c_int, + pub ifru_mtu: c_int, + pub ifru_phys: c_int, + pub ifru_media: c_int, + pub ifru_intval: c_int, + pub ifru_data: *mut c_char, + pub ifru_devmtu: ifdevmtu, + pub ifru_kpi: ifkpi, + pub ifru_wake_flags: u32, + pub ifru_route_refcnt: u32, + pub ifru_cap: [c_int; 2], + pub ifru_functional_type: u32, +} + +#[repr(C)] +pub struct ifreq { + pub ifr_name: [c_char; IFNAMSIZ], + pub ifr_ifru: ifr_ifru, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct in6_addrlifetime{ + pub ia6t_expire: time_t, + pub ia6t_preferred: time_t, + pub ia6t_vltime: u32, + pub ia6t_pltime: u32, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct in6_ifstat { + pub ifs6_in_receive: u64, + pub ifs6_in_hdrerr: u64, + pub ifs6_in_toobig: u64, + pub ifs6_in_noroute: u64, + pub ifs6_in_addrerr: u64, + pub ifs6_in_protounknown: u64, + pub ifs6_in_truncated: u64, + pub ifs6_in_discard: u64, + pub ifs6_in_deliver: u64, + pub ifs6_out_forward: u64, + pub ifs6_out_request: u64, + pub ifs6_out_discard: u64, + pub ifs6_out_fragok: u64, + pub ifs6_out_fragfail: u64, + pub ifs6_out_fragcreat: u64, + pub ifs6_reass_reqd: u64, + pub ifs6_reass_ok: u64, + pub ifs6_atmfrag_rcvd: u64, + pub ifs6_reass_fail: u64, + pub ifs6_in_mcast: u64, + pub ifs6_out_mcast: u64, + pub ifs6_cantfoward_icmp6: u64, + pub ifs6_addr_expiry_cnt: u64, + pub ifs6_pfx_expiry_cnt: u64, + pub ifs6_defrtr_expiry_cnt: u64, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct icmp6_ifstat { + pub ifs6_in_msg: u64, + pub ifs6_in_error: u64, + pub ifs6_in_dstunreach: u64, + pub ifs6_in_adminprohib: u64, + pub ifs6_in_timeexceed: u64, + pub ifs6_in_paramprob: u64, + pub ifs6_in_pkttoobig: u64, + pub ifs6_in_echo: u64, + pub ifs6_in_echoreply: u64, + pub ifs6_in_routersolicit: u64, + pub ifs6_in_routeradvert: u64, + pub ifs6_in_neighborsolicit: u64, + pub ifs6_in_neighboradvert: u64, + pub ifs6_in_redirect: u64, + pub ifs6_in_mldquery: u64, + pub ifs6_in_mldreport: u64, + pub ifs6_in_mlddone: u64, + pub ifs6_out_msg: u64, + pub ifs6_out_error: u64, + pub ifs6_out_dstunreach: u64, + pub ifs6_out_adminprohib: u64, + pub ifs6_out_timeexceed: u64, + pub ifs6_out_paramprob: u64, + pub ifs6_out_pkttoobig: u64, + pub ifs6_out_echo: u64, + pub ifs6_out_echoreply: u64, + pub ifs6_out_routersolicit: u64, + pub ifs6_out_routeradvert: u64, + pub ifs6_out_neighborsolicit: u64, + pub ifs6_out_neighboradvert: u64, + pub ifs6_out_redirect: u64, + pub ifs6_out_mldquery: u64, + pub ifs6_out_mldreport: u64, + pub ifs6_out_mlddone: u64, +} + +#[repr(C)] +pub union ifr_ifru6 { + pub ifru_addr: sockaddr_in6, + pub ifru_dstaddr: sockaddr_in6, + pub ifru_flags: c_int, + pub ifru_flags6: c_int, + pub ifru_metric: c_int, + pub ifru_intval: c_int, + pub ifru_data: *mut c_char, + pub ifru_lifetime: in6_addrlifetime, // ifru_lifetime + pub ifru_stat: in6_ifstat, + pub ifru_icmp6stat: icmp6_ifstat, + pub ifru_scope_id: [u32; SCOPE6_ID_MAX] +} + +#[repr(C)] +pub struct in6_ifreq { + pub ifr_name: [c_char; IFNAMSIZ], + pub ifr_ifru: ifr_ifru6, +} + +pub const SIOCSIFADDR: c_ulong = request_code_write!(b'i', 12, mem::size_of::()); +pub const SIOCSIFADDR_IN6: c_ulong = request_code_write!(b'i', 12, mem::size_of::()); +pub const SIOCGIFMTU: c_ulong = request_code_readwrite!(b'i', 51, mem::size_of::()); +pub const SIOCSIFMTU: c_ulong = request_code_write!(b'i', 52, mem::size_of::()); +pub const SIOCGIFNETMASK: c_ulong = request_code_readwrite!(b'i', 37, mem::size_of::()); +pub const SIOCSIFNETMASK: c_ulong = request_code_write!(b'i', 22, mem::size_of::()); + +#[macro_export] +macro_rules! syscall { + ($call: ident ( $($arg: expr),* $(,)* ) ) => {{ + match unsafe { ::libc::$call($($arg, )*) } { + -1 => Err(::std::io::Error::last_os_error()), + res => Ok(res), + } + }}; +} + +pub use syscall; + +ioctl_readwrite!(resolve_ctl_info, b'N', 3, ctl_info); +ioctl_read_bad!(if_get_addr, libc::SIOCGIFADDR, ifreq); +ioctl_read_bad!(if_get_mtu, SIOCGIFMTU, ifreq); +ioctl_read_bad!(if_get_netmask, SIOCGIFNETMASK, ifreq); +ioctl_write_ptr_bad!(if_set_addr, SIOCSIFADDR, ifreq); +ioctl_write_ptr_bad!(if_set_addr6, SIOCSIFADDR_IN6, in6_ifreq); +ioctl_write_ptr_bad!(if_set_mtu, SIOCSIFMTU, ifreq); +ioctl_write_ptr_bad!(if_set_netmask, SIOCSIFNETMASK, ifreq); diff --git a/tun/src/unix/linux/mod.rs b/tun/src/unix/linux/mod.rs new file mode 100644 index 0000000..60d6341 --- /dev/null +++ b/tun/src/unix/linux/mod.rs @@ -0,0 +1,221 @@ +use std::{ + fs::OpenOptions, + io::{Error, Write}, + mem, + net::{Ipv4Addr, Ipv6Addr, SocketAddrV4}, + os::{ + fd::RawFd, + unix::io::{AsRawFd, FromRawFd, IntoRawFd}, + }, +}; + +use fehler::throws; +use libc::in6_ifreq; +use socket2::{Domain, SockAddr, Socket, Type}; +use tracing::{info, instrument}; + +use super::{ifname_to_string, string_to_ifname}; +use crate::TunOptions; + +mod sys; + +#[derive(Debug)] +pub struct TunInterface { + pub(crate) socket: socket2::Socket, +} + +impl TunInterface { + #[throws] + #[instrument] + pub fn new() -> TunInterface { + Self::new_with_options(TunOptions::new())? + } + + #[throws] + #[instrument] + pub(crate) fn new_with_options(options: TunOptions) -> TunInterface { + let file = OpenOptions::new() + .read(true) + .write(true) + .open("/dev/net/tun")?; + + let mut flags = libc::IFF_TUN as i16; + + if options.no_pi { + flags |= libc::IFF_NO_PI as i16; + } + if options.tun_excl { + flags |= libc::IFF_TUN_EXCL as i16; + } + + let name = options + .name + .map(|name| string_to_ifname(&name)) + .unwrap_or([0; libc::IFNAMSIZ]); + + let iff = libc::ifreq { + ifr_name: name, + ifr_ifru: libc::__c_anonymous_ifr_ifru { ifru_flags: flags }, + }; + unsafe { sys::tun_set_iff(file.as_raw_fd(), &iff)? }; + + let socket = unsafe { socket2::Socket::from_raw_fd(file.into_raw_fd()) }; + TunInterface { socket } + } + + #[throws] + #[instrument] + pub fn name(&self) -> String { + let mut iff = unsafe { mem::zeroed() }; + unsafe { sys::tun_get_iff(self.socket.as_raw_fd(), &mut iff)? }; + ifname_to_string(iff.ifr_name) + } + + #[throws] + #[instrument] + fn ifreq(&self) -> sys::ifreq { + let mut iff: sys::ifreq = unsafe { mem::zeroed() }; + iff.ifr_name = string_to_ifname(&self.name()?); + iff + } + + #[throws] + #[instrument] + fn in6_ifreq(&self) -> in6_ifreq { + let mut iff: in6_ifreq = unsafe { mem::zeroed() }; + iff.ifr6_ifindex = self.index()?; + iff + } + + #[throws] + #[instrument] + pub fn index(&self) -> i32 { + let mut iff = self.ifreq()?; + self.perform(|fd| unsafe { sys::if_get_index(fd, &mut iff) })?; + unsafe { iff.ifr_ifru.ifru_ifindex } + } + + #[throws] + #[instrument] + pub fn set_ipv4_addr(&self, addr: Ipv4Addr) { + let addr = SockAddr::from(SocketAddrV4::new(addr, 0)); + let mut iff = self.ifreq()?; + iff.ifr_ifru.ifru_addr = unsafe { *addr.as_ptr() }; + self.perform(|fd| unsafe { sys::if_set_addr(fd, &iff) })?; + info!("ipv4_addr_set: {:?} (fd: {:?})", addr, self.as_raw_fd()) + } + + #[throws] + #[instrument] + pub fn ipv4_addr(&self) -> Ipv4Addr { + let mut iff = self.ifreq()?; + self.perform(|fd| unsafe { sys::if_get_addr(fd, &mut iff) })?; + let addr = unsafe { *(&iff.ifr_ifru.ifru_addr as *const _ as *const sys::sockaddr_in) }; + Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr)) + } + + #[throws] + #[instrument] + pub fn set_broadcast_addr(&self, addr: Ipv4Addr) { + let addr = SockAddr::from(SocketAddrV4::new(addr, 0)); + let mut iff = self.ifreq()?; + iff.ifr_ifru.ifru_broadaddr = unsafe { *addr.as_ptr() }; + self.perform(|fd| unsafe { sys::if_set_brdaddr(fd, &iff) })?; + info!( + "broadcast_addr_set: {:?} (fd: {:?})", + addr, + self.as_raw_fd() + ) + } + + #[throws] + #[instrument] + pub fn broadcast_addr(&self) -> Ipv4Addr { + let mut iff = self.ifreq()?; + self.perform(|fd| unsafe { sys::if_get_brdaddr(fd, &mut iff) })?; + let addr = + unsafe { *(&iff.ifr_ifru.ifru_broadaddr as *const _ as *const sys::sockaddr_in) }; + Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr)) + } + + #[throws] + #[instrument] + pub fn set_ipv6_addr(&self, addr: Ipv6Addr) { + let mut iff = self.in6_ifreq()?; + iff.ifr6_addr.s6_addr = addr.octets(); + self.perform6(|fd| unsafe { sys::if_set_addr6(fd, &iff) })?; + info!("ipv6_addr_set: {:?} (fd: {:?})", addr, self.as_raw_fd()) + } + + #[throws] + #[instrument] + pub fn set_mtu(&self, mtu: i32) { + let mut iff = self.ifreq()?; + iff.ifr_ifru.ifru_mtu = mtu; + self.perform(|fd| unsafe { sys::if_set_mtu(fd, &iff) })?; + info!("mtu_set: {:?} (fd: {:?})", mtu, self.as_raw_fd()) + } + + #[throws] + #[instrument] + pub fn mtu(&self) -> i32 { + let mut iff = self.ifreq()?; + self.perform(|fd| unsafe { sys::if_get_mtu(fd, &mut iff) })?; + let mtu = unsafe { iff.ifr_ifru.ifru_mtu }; + + mtu + } + + #[throws] + #[instrument] + pub fn set_netmask(&self, addr: Ipv4Addr) { + let addr = SockAddr::from(SocketAddrV4::new(addr, 0)); + + let mut iff = self.ifreq()?; + iff.ifr_ifru.ifru_netmask = unsafe { *addr.as_ptr() }; + + self.perform(|fd| unsafe { sys::if_set_netmask(fd, &iff) })?; + + info!( + "netmask_set: {:?} (fd: {:?})", + unsafe { iff.ifr_ifru.ifru_netmask }, + self.as_raw_fd() + ) + } + + #[throws] + #[instrument] + pub fn netmask(&self) -> Ipv4Addr { + let mut iff = self.ifreq()?; + self.perform(|fd| unsafe { sys::if_get_netmask(fd, &mut iff) })?; + + let netmask = + unsafe { *(&iff.ifr_ifru.ifru_netmask as *const _ as *const sys::sockaddr_in) }; + + Ipv4Addr::from(u32::from_be(netmask.sin_addr.s_addr)) + } + + #[throws] + fn perform(&self, perform: impl FnOnce(RawFd) -> Result) -> R { + let span = tracing::info_span!("perform"); + let _enter = span.enter(); + + let socket = Socket::new(Domain::IPV4, Type::DGRAM, None)?; + perform(socket.as_raw_fd())? + } + + #[throws] + fn perform6(&self, perform: impl FnOnce(RawFd) -> Result) -> R { + let span = tracing::info_span!("perform"); + let _enter = span.enter(); + + let socket = Socket::new(Domain::IPV6, Type::DGRAM, None)?; + perform(socket.as_raw_fd())? + } + + #[throws] + #[instrument] + pub fn send(&self, buf: &[u8]) -> usize { + self.socket.send(buf)? + } +} diff --git a/tun/src/unix/linux/sys.rs b/tun/src/unix/linux/sys.rs new file mode 100644 index 0000000..e12c8ec --- /dev/null +++ b/tun/src/unix/linux/sys.rs @@ -0,0 +1,26 @@ +use std::mem::size_of; + +pub use libc::{ifreq, sockaddr, sockaddr_in, sockaddr_in6}; +use nix::{ioctl_read_bad, ioctl_write_ptr_bad, request_code_read, request_code_write}; + +ioctl_write_ptr_bad!( + tun_set_iff, + request_code_write!(b'T', 202, size_of::()), + libc::ifreq +); +ioctl_read_bad!( + tun_get_iff, + request_code_read!(b'T', 210, size_of::()), + libc::ifreq +); +ioctl_read_bad!(if_get_index, libc::SIOCGIFINDEX, libc::ifreq); +ioctl_read_bad!(if_get_addr, libc::SIOCGIFADDR, libc::ifreq); +ioctl_read_bad!(if_get_brdaddr, libc::SIOCGIFBRDADDR, libc::ifreq); +ioctl_read_bad!(if_get_mtu, libc::SIOCGIFMTU, libc::ifreq); +ioctl_read_bad!(if_get_netmask, libc::SIOCGIFNETMASK, libc::ifreq); + +ioctl_write_ptr_bad!(if_set_addr, libc::SIOCSIFADDR, libc::ifreq); +ioctl_write_ptr_bad!(if_set_addr6, libc::SIOCSIFADDR, libc::in6_ifreq); +ioctl_write_ptr_bad!(if_set_brdaddr, libc::SIOCSIFBRDADDR, libc::ifreq); +ioctl_write_ptr_bad!(if_set_mtu, libc::SIOCSIFMTU, libc::ifreq); +ioctl_write_ptr_bad!(if_set_netmask, libc::SIOCSIFNETMASK, libc::ifreq); diff --git a/tun/src/unix/mod.rs b/tun/src/unix/mod.rs new file mode 100644 index 0000000..ae0b77a --- /dev/null +++ b/tun/src/unix/mod.rs @@ -0,0 +1,82 @@ +use std::{ + io::Error, + mem::MaybeUninit, + os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd}, +}; + +use tracing::instrument; + +mod queue; + +#[cfg(target_vendor = "apple")] +#[path = "apple/mod.rs"] +mod imp; + +#[cfg(target_os = "linux")] +#[path = "linux/mod.rs"] +mod imp; + +use fehler::throws; +pub use imp::TunInterface; +pub use queue::TunQueue; + +impl AsRawFd for TunInterface { + fn as_raw_fd(&self) -> RawFd { + self.socket.as_raw_fd() + } +} + +impl FromRawFd for TunInterface { + unsafe fn from_raw_fd(fd: RawFd) -> TunInterface { + let socket = socket2::Socket::from_raw_fd(fd); + TunInterface { socket } + } +} + +impl IntoRawFd for TunInterface { + fn into_raw_fd(self) -> RawFd { + self.socket.into_raw_fd() + } +} + +unsafe fn assume_init(buf: &[MaybeUninit]) -> &[u8] { + &*(buf as *const [MaybeUninit] as *const [u8]) +} + +impl TunInterface { + #[throws] + #[instrument] + pub fn recv(&self, buf: &mut [u8]) -> usize { + // Use IoVec to read directly into target buffer + let mut tmp_buf = [MaybeUninit::uninit(); 1500]; + let len = self.socket.recv(&mut tmp_buf)?; + let result_buf = unsafe { assume_init(&tmp_buf[4..len]) }; + buf[..len - 4].copy_from_slice(result_buf); + len - 4 + } + + #[throws] + #[instrument] + pub fn set_nonblocking(&mut self, nb: bool) { + self.socket.set_nonblocking(nb)?; + } +} + +#[instrument] +pub fn ifname_to_string(buf: [libc::c_char; libc::IFNAMSIZ]) -> String { + // TODO: Switch to `CStr::from_bytes_until_nul` when stabilized + unsafe { + std::ffi::CStr::from_ptr(buf.as_ptr() as *const _) + .to_str() + .unwrap() + .to_string() + } +} + +#[instrument] +pub fn string_to_ifname(name: &str) -> [libc::c_char; libc::IFNAMSIZ] { + let mut buf = [0 as libc::c_char; libc::IFNAMSIZ]; + let len = name.len().min(buf.len()); + buf[..len].copy_from_slice(unsafe { &*(name.as_bytes() as *const _ as *const [libc::c_char]) }); + buf +} diff --git a/tun/src/unix/queue.rs b/tun/src/unix/queue.rs new file mode 100644 index 0000000..879dcd5 --- /dev/null +++ b/tun/src/unix/queue.rs @@ -0,0 +1,59 @@ +use std::{ + io::{Error, Read, Write}, + mem::MaybeUninit, + os::unix::io::{AsRawFd, IntoRawFd, RawFd}, +}; + +use fehler::throws; +use tracing::instrument; + +use crate::TunInterface; + +#[derive(Debug)] +pub struct TunQueue { + socket: socket2::Socket, +} + +impl TunQueue { + #[instrument] + pub fn recv(&self, buf: &mut [MaybeUninit]) -> Result { + self.socket.recv(buf) + } +} + +impl Read for TunQueue { + #[throws] + fn read(&mut self, buf: &mut [u8]) -> usize { + self.socket.read(buf)? + } +} + +impl Write for TunQueue { + #[throws] + fn write(&mut self, buf: &[u8]) -> usize { + self.socket.write(buf)? + } + + #[throws] + fn flush(&mut self) { + self.socket.flush()? + } +} + +impl From for TunQueue { + fn from(interface: TunInterface) -> TunQueue { + TunQueue { socket: interface.socket } + } +} + +impl AsRawFd for TunQueue { + fn as_raw_fd(&self) -> RawFd { + self.socket.as_raw_fd() + } +} + +impl IntoRawFd for TunQueue { + fn into_raw_fd(self) -> RawFd { + self.socket.into_raw_fd() + } +} diff --git a/tun/src/windows/mod.rs b/tun/src/windows/mod.rs index 6c831d1..dadd53f 100644 --- a/tun/src/windows/mod.rs +++ b/tun/src/windows/mod.rs @@ -1,28 +1,45 @@ -use std::io::Result; -use std::ptr; -use widestring::{u16cstr, U16CString}; +use std::{fmt::Debug, io::Error, ptr}; +use fehler::throws; +use widestring::U16CString; +use windows::Win32::Foundation::GetLastError; mod queue; pub use queue::TunQueue; +use super::TunOptions; + pub struct TunInterface { - wintun: sys::wintun, handle: sys::WINTUN_ADAPTER_HANDLE, name: String, } +impl Debug for TunInterface { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TunInterface") + .field("handle", &"SYS_WINTUN_ADAPTER_HANDLE".to_string()) + .field("name", &self.name) + .finish() + } +} + impl TunInterface { - pub fn new() -> Result { - let name = U16CString::from(u16cstr!("ConradNet")); - let wintun = sys::wintun::default(); + #[throws] + pub fn new() -> TunInterface { + Self::new_with_options(TunOptions::new())? + } + + #[throws] + pub(crate) fn new_with_options(options: TunOptions) -> TunInterface { + let name_owned = options.name.unwrap_or("Burrow".to_owned()); + let name = U16CString::from_str(&name_owned).unwrap(); + let handle = - unsafe { wintun.WintunCreateAdapter(name.as_ptr(), name.as_ptr(), ptr::null()) }; - Ok(TunInterface { - wintun, - handle, - name: String::from("ConradNet"), - }) + unsafe { sys::WINTUN.WintunCreateAdapter(name.as_ptr(), name.as_ptr(), ptr::null()) }; + if handle.is_null() { + unsafe { GetLastError() }.ok()? + } + TunInterface { handle, name: name_owned } } pub fn name(&self) -> String { @@ -32,17 +49,25 @@ impl TunInterface { impl Drop for TunInterface { fn drop(&mut self) { - unsafe { self.wintun.WintunCloseAdapter(self.handle) } + unsafe { sys::WINTUN.WintunCloseAdapter(self.handle) } } } pub(crate) mod sys { - #![allow(dead_code, non_camel_case_types, non_snake_case)] + #![allow(clippy::all, dead_code, non_camel_case_types, non_snake_case)] include!(concat!(env!("OUT_DIR"), "/wintun.rs")); - impl Default for wintun { - fn default() -> Self { - unsafe { wintun::new(format!("{}/wintun.dll", env!("OUT_DIR"))).unwrap() } - } + const WINTUN_BINARY: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/wintun.dll")); + + lazy_static::lazy_static! { + pub static ref WINTUN: wintun = { + use std::io::Write; + + let mut temp_file = tempfile::NamedTempFile::new().unwrap(); + temp_file.write_all(&WINTUN_BINARY).unwrap(); + let (_, path) = temp_file.keep().unwrap(); + + unsafe { wintun::new(&path) }.unwrap() + }; } } diff --git a/tun/src/windows/queue.rs b/tun/src/windows/queue.rs index 609da89..8fa9e19 100644 --- a/tun/src/windows/queue.rs +++ b/tun/src/windows/queue.rs @@ -1 +1,2 @@ +#[derive(Debug)] pub struct TunQueue; diff --git a/tun/tests/configure.rs b/tun/tests/configure.rs new file mode 100644 index 0000000..e7e2c6d --- /dev/null +++ b/tun/tests/configure.rs @@ -0,0 +1,79 @@ +use std::{io::Error, net::Ipv4Addr}; + +use fehler::throws; +use tun::TunInterface; + +#[test] +#[throws] +fn test_create() { + TunInterface::new()?; +} + +#[test] +#[throws] +#[cfg(not(any(target_os = "windows", target_vendor = "apple")))] +fn test_set_get_broadcast_addr() { + let tun = TunInterface::new()?; + let addr = Ipv4Addr::new(10, 0, 0, 1); + tun.set_ipv4_addr(addr)?; + + let broadcast_addr = Ipv4Addr::new(255, 255, 255, 0); + tun.set_broadcast_addr(broadcast_addr)?; + let result = tun.broadcast_addr()?; + + assert_eq!(broadcast_addr, result); +} + +#[test] +#[throws] +#[cfg(not(target_os = "windows"))] +fn test_set_get_ipv4() { + let tun = TunInterface::new()?; + + let addr = Ipv4Addr::new(10, 0, 0, 1); + tun.set_ipv4_addr(addr)?; + let result = tun.ipv4_addr()?; + + assert_eq!(addr, result); +} + +#[test] +#[throws] +#[cfg(not(any(target_os = "windows", target_vendor = "apple")))] +fn test_set_get_ipv6() { + use std::net::Ipv6Addr; + + let tun = TunInterface::new()?; + + let addr = Ipv6Addr::new(1, 1, 1, 1, 1, 1, 1, 1); + tun.set_ipv6_addr(addr)?; + + // let result = tun.ipv6_addr()?; + // assert_eq!(addr, result); +} + +#[test] +#[throws] +#[cfg(not(target_os = "windows"))] +fn test_set_get_mtu() { + let interf = TunInterface::new()?; + + interf.set_mtu(500)?; + + assert_eq!(interf.mtu().unwrap(), 500); +} + +#[test] +#[throws] +#[cfg(not(target_os = "windows"))] +fn test_set_get_netmask() { + let interf = TunInterface::new()?; + + let netmask = Ipv4Addr::new(255, 0, 0, 0); + let addr = Ipv4Addr::new(192, 168, 1, 1); + + interf.set_ipv4_addr(addr)?; + interf.set_netmask(netmask)?; + + assert_eq!(interf.netmask()?, netmask); +} diff --git a/tun/tests/packets.rs b/tun/tests/packets.rs new file mode 100644 index 0000000..80c078b --- /dev/null +++ b/tun/tests/packets.rs @@ -0,0 +1,48 @@ +use std::{io::Error, net::Ipv4Addr}; +use std::net::Ipv6Addr; + +use fehler::throws; +use tun::TunInterface; + +#[throws] +#[test] +#[ignore = "requires interactivity"] +#[cfg(not(target_os = "windows"))] +fn tst_read() { + // This test is interactive, you need to send a packet to any server through + // 192.168.1.10 EG. `sudo route add 8.8.8.8 192.168.1.10`, + //`dig @8.8.8.8 hackclub.com` + let tun = TunInterface::new()?; + println!("tun name: {:?}", tun.name()?); + tun.set_ipv4_addr(Ipv4Addr::from([192, 168, 1, 10]))?; + println!("tun ip: {:?}", tun.ipv4_addr()?); + println!("Waiting for a packet..."); + let buf = &mut [0u8; 1500]; + let res = tun.recv(buf); + println!("Received!"); + assert!(res.is_ok()); +} + +#[test] +#[throws] +#[ignore = "requires interactivity"] +#[cfg(not(target_os = "windows"))] +fn write_packets() { + let tun = TunInterface::new()?; + let mut buf = [0u8; 1500]; + buf[0] = 6 << 4; + let bytes_written = tun.send(&buf)?; + assert_eq!(bytes_written, 1504); +} + +#[test] +#[throws] +#[ignore = "requires interactivity"] +#[cfg(not(target_os = "windows"))] +fn set_ipv6() { + let tun = TunInterface::new()?; + println!("tun name: {:?}", tun.name()?); + let targ_addr: Ipv6Addr = "::1".parse().unwrap(); + println!("v6 addr: {:?}", targ_addr); + tun.set_ipv6_addr(targ_addr)?; +} \ No newline at end of file diff --git a/tun/tests/tokio.rs b/tun/tests/tokio.rs new file mode 100644 index 0000000..f7cb273 --- /dev/null +++ b/tun/tests/tokio.rs @@ -0,0 +1,22 @@ +use std::net::Ipv4Addr; + +#[tokio::test] +#[cfg(all(feature = "tokio", not(target_os = "windows")))] +async fn test_create() { + let tun = tun::TunInterface::new().unwrap(); + let _ = tun::tokio::TunInterface::new(tun).unwrap(); +} + +#[tokio::test] +#[ignore = "requires interactivity"] +#[cfg(all(feature = "tokio", not(target_os = "windows")))] +async fn test_write() { + let tun = tun::TunInterface::new().unwrap(); + tun.set_ipv4_addr(Ipv4Addr::from([192, 168, 1, 10])) + .unwrap(); + let async_tun = tun::tokio::TunInterface::new(tun).unwrap(); + let mut buf = [0u8; 1500]; + buf[0] = 6 << 4; + let bytes_written = async_tun.send(&buf).await.unwrap(); + assert!(bytes_written > 0); +}