diff --git a/.github/workflows/_build_artifacts.yml b/.github/workflows/_build_artifacts.yml index 9da867ff7..41330ccc0 100644 --- a/.github/workflows/_build_artifacts.yml +++ b/.github/workflows/_build_artifacts.yml @@ -156,9 +156,9 @@ jobs: artifact: firezone-client-headless-linux image_name: client # mark:next-headless-version - release_name: headless-client-1.1.8 + release_name: headless-client-1.2.0 # mark:next-headless-version - version: 1.1.8 + version: 1.2.0 - package: firezone-relay artifact: firezone-relay image_name: relay @@ -166,9 +166,9 @@ jobs: artifact: firezone-gateway image_name: gateway # mark:next-gateway-version - release_name: gateway-1.1.6 + release_name: gateway-1.2.0 # mark:next-gateway-version - version: 1.1.6 + version: 1.2.0 - package: snownet-tests artifact: snownet-tests image_name: snownet-tests @@ -352,7 +352,7 @@ jobs: - name: relay - name: gateway # mark:next-gateway-version - version: 1.1.6 + version: 1.2.0 - name: client # mark:next-client-version version: 1.0.6 diff --git a/.github/workflows/_tauri.yml b/.github/workflows/_tauri.yml index 48e2f848c..f6aa93019 100644 --- a/.github/workflows/_tauri.yml +++ b/.github/workflows/_tauri.yml @@ -26,31 +26,31 @@ jobs: include: - runs-on: ubuntu-20.04 # mark:next-gui-version - binary-dest-path: firezone-client-gui-linux_1.1.13_x86_64 + binary-dest-path: firezone-client-gui-linux_1.2.0_x86_64 rename-script: ../../scripts/build/tauri-rename-ubuntu.sh upload-script: ../../scripts/build/tauri-upload-ubuntu.sh # mark:next-gui-version - syms-artifact: rust/gui-client/firezone-client-gui-linux_1.1.13_x86_64.dwp + syms-artifact: rust/gui-client/firezone-client-gui-linux_1.2.0_x86_64.dwp # mark:next-gui-version - pkg-artifact: rust/gui-client/firezone-client-gui-linux_1.1.13_x86_64.deb + pkg-artifact: rust/gui-client/firezone-client-gui-linux_1.2.0_x86_64.deb - runs-on: ubuntu-20.04-arm # mark:next-gui-version - binary-dest-path: firezone-client-gui-linux_1.1.13_aarch64 + binary-dest-path: firezone-client-gui-linux_1.2.0_aarch64 rename-script: ../../scripts/build/tauri-rename-ubuntu.sh upload-script: ../../scripts/build/tauri-upload-ubuntu.sh # mark:next-gui-version - syms-artifact: rust/gui-client/firezone-client-gui-linux_1.1.13_aarch64.dwp + syms-artifact: rust/gui-client/firezone-client-gui-linux_1.2.0_aarch64.dwp # mark:next-gui-version - pkg-artifact: rust/gui-client/firezone-client-gui-linux_1.1.13_aarch64.deb + pkg-artifact: rust/gui-client/firezone-client-gui-linux_1.2.0_aarch64.deb - runs-on: windows-2019 # mark:next-gui-version - binary-dest-path: firezone-client-gui-windows_1.1.13_x86_64 + binary-dest-path: firezone-client-gui-windows_1.2.0_x86_64 rename-script: ../../scripts/build/tauri-rename-windows.sh upload-script: ../../scripts/build/tauri-upload-windows.sh # mark:next-gui-version - syms-artifact: rust/gui-client/firezone-client-gui-windows_1.1.13_x86_64.pdb + syms-artifact: rust/gui-client/firezone-client-gui-windows_1.2.0_x86_64.pdb # mark:next-gui-version - pkg-artifact: rust/gui-client/firezone-client-gui-windows_1.1.13_x86_64.msi + pkg-artifact: rust/gui-client/firezone-client-gui-windows_1.2.0_x86_64.msi env: BINARY_DEST_PATH: ${{ matrix.binary-dest-path }} AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }} @@ -96,7 +96,7 @@ jobs: if: ${{ runner.os == 'Windows' }} shell: bash # mark:next-gui-version - run: ../../scripts/build/sign.sh ../target/release/bundle/msi/Firezone_1.1.13_x64_en-US.msi + run: ../../scripts/build/sign.sh ../target/release/bundle/msi/Firezone_1.2.0_x64_en-US.msi - name: Rename artifacts and compute SHA256 shell: bash run: ${{ matrix.rename-script }} @@ -121,6 +121,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPOSITORY: ${{ github.repository }} # mark:next-gui-version - TAG_NAME: gui-client-1.1.13 + TAG_NAME: gui-client-1.2.0 shell: bash run: ${{ matrix.upload-script }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d0cea0c3..ebbc9a247 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,13 +46,13 @@ jobs: matrix: include: # mark:next-gateway-version - - release_name: gateway-1.1.6 + - release_name: gateway-1.2.0 config_name: release-drafter-gateway.yml # mark:next-headless-version - - release_name: headless-client-1.1.8 + - release_name: headless-client-1.2.0 config_name: release-drafter-headless-client.yml # mark:next-gui-version - - release_name: gui-client-1.1.13 + - release_name: gui-client-1.2.0 config_name: release-drafter-gui-client.yml steps: - uses: release-drafter/release-drafter@v6 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3ba3cc0f0..bf105f57c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -40,11 +40,11 @@ jobs: if [[ "${{ github.event.release.name }}" =~ gateway* ]]; then ARTIFACT=gateway # mark:next-gateway-version - VERSION="1.1.6" + VERSION="1.2.0" elif [[ "${{ github.event.release.name }}" =~ headless* ]]; then ARTIFACT=client # mark:next-headless-version - VERSION="1.1.8" + VERSION="1.2.0" else echo "Release doesn't require publishing Docker images" exit 0 diff --git a/kotlin/android/app/build.gradle.kts b/kotlin/android/app/build.gradle.kts index c65eb819d..7087e779e 100644 --- a/kotlin/android/app/build.gradle.kts +++ b/kotlin/android/app/build.gradle.kts @@ -56,7 +56,7 @@ android { targetSdk = 35 versionCode = (System.currentTimeMillis() / 1000 / 10).toInt() // mark:next-android-version - versionName = "1.1.7" + versionName = "1.2.0" multiDexEnabled = true testInstrumentationRunner = "dev.firezone.android.core.HiltTestRunner" } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index adc8c93a9..7f545bdae 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -878,6 +878,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim 0.11.0", + "terminal_size", ] [[package]] @@ -988,9 +989,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "condtype" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" + [[package]] name = "connlib-client-android" -version = "1.1.7" +version = "1.2.0" dependencies = [ "android_log-sys", "backoff", @@ -1016,7 +1023,7 @@ dependencies = [ [[package]] name = "connlib-client-apple" -version = "1.1.6" +version = "1.2.0" dependencies = [ "anyhow", "backoff", @@ -1516,6 +1523,31 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "divan" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d567df2c9c2870a43f3f2bd65aaeb18dbce1c18f217c3e564b4fbaeb3ee56c" +dependencies = [ + "cfg-if", + "clap", + "condtype", + "divan-macros", + "libc", + "regex-lite", +] + +[[package]] +name = "divan-macros" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "dns-lookup" version = "2.0.4" @@ -1796,7 +1828,7 @@ dependencies = [ [[package]] name = "firezone-gateway" -version = "1.1.6" +version = "1.2.0" dependencies = [ "anyhow", "async-trait", @@ -1832,7 +1864,7 @@ dependencies = [ [[package]] name = "firezone-gui-client" -version = "1.1.13" +version = "1.2.0" dependencies = [ "anyhow", "arboard", @@ -1884,7 +1916,7 @@ dependencies = [ [[package]] name = "firezone-headless-client" -version = "1.1.8" +version = "1.2.0" dependencies = [ "anyhow", "atomicwrites", @@ -1991,11 +2023,13 @@ dependencies = [ "chrono", "connlib-shared", "derivative", + "divan", "domain", "firezone-logging", "firezone-relay", "futures", "futures-util", + "glob", "hex", "ip-packet", "ip_network", @@ -2011,6 +2045,7 @@ dependencies = [ "snownet", "socket-factory", "socket2", + "test-case", "test-strategy", "thiserror", "tokio", @@ -4893,6 +4928,12 @@ dependencies = [ "regex-syntax 0.8.3", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -6157,6 +6198,49 @@ dependencies = [ "utf-8", ] +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", + "test-case-core", +] + [[package]] name = "test-strategy" version = "0.3.1" diff --git a/rust/connlib/clients/android/Cargo.toml b/rust/connlib/clients/android/Cargo.toml index 879d32efd..b1d421b19 100644 --- a/rust/connlib/clients/android/Cargo.toml +++ b/rust/connlib/clients/android/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "connlib-client-android" # mark:next-android-version -version = "1.1.7" +version = "1.2.0" edition = "2021" [lib] diff --git a/rust/connlib/clients/apple/Cargo.toml b/rust/connlib/clients/apple/Cargo.toml index f47beec99..cc81689f4 100644 --- a/rust/connlib/clients/apple/Cargo.toml +++ b/rust/connlib/clients/apple/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "connlib-client-apple" # mark:next-apple-version -version = "1.1.6" +version = "1.2.0" edition = "2021" [features] diff --git a/rust/connlib/tunnel/Cargo.toml b/rust/connlib/tunnel/Cargo.toml index f2a48b467..e4af3c182 100644 --- a/rust/connlib/tunnel/Cargo.toml +++ b/rust/connlib/tunnel/Cargo.toml @@ -10,10 +10,12 @@ boringtun = { workspace = true } bytes = { version = "1.7", default-features = false, features = ["std"] } chrono = { workspace = true } connlib-shared = { workspace = true } +divan = { version = "0.1.14", optional = true } domain = { workspace = true } firezone-logging = { workspace = true } futures = { version = "0.3", default-features = false, features = ["std", "async-await", "executor"] } futures-util = { version = "0.3", default-features = false, features = ["std", "async-await", "async-await-macro"] } +glob = "0.3.1" hex = "0.4.3" ip-packet = { workspace = true } ip_network = { version = "0.4", default-features = false } @@ -41,12 +43,19 @@ ip-packet = { workspace = true, features = ["proptest"] } proptest-state-machine = "0.3" rand = "0.8" serde_json = "1.0" +test-case = "3.3.1" test-strategy = "0.3.1" tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +[[bench]] +name = "divan" +harness = false +required-features = ["divan"] + [features] proptest = ["dep:proptest"] +divan = ["dep:divan"] [lints] workspace = true diff --git a/rust/connlib/tunnel/benches/divan.rs b/rust/connlib/tunnel/benches/divan.rs new file mode 100644 index 000000000..5a32eb889 --- /dev/null +++ b/rust/connlib/tunnel/benches/divan.rs @@ -0,0 +1,5 @@ +extern crate firezone_tunnel; // Ensure benchmarks aren't optimised out. + +fn main() { + divan::main() +} diff --git a/rust/connlib/tunnel/proptest-regressions/tests.txt b/rust/connlib/tunnel/proptest-regressions/tests.txt index 49132957a..4fa635605 100644 --- a/rust/connlib/tunnel/proptest-regressions/tests.txt +++ b/rust/connlib/tunnel/proptest-regressions/tests.txt @@ -75,3 +75,4 @@ cc 40d8dea79bdce732cacdfd9e36c630d988c83eafade97b580f62aae242163874 # shrinks to cc 4c68063d59ec9f783eaf23d2398e4fc74d1da3b4b3dba0f38c2a867079878d7c cc 62bc168a511fbce3ff97f448a37bc7817dc0e22d61cc32c00fd6563bdfe4de90 cc ada50b62702f5a45ccea7553bd3daec01dc8df68c6ac8e2b4c996aba8c2cc3fe # shrinks to (initial_state, transitions, seen_counter) = (ReferenceState { client: Host { inner: RefClient { id: dc8a73db-d5f0-8f52-1788-0c5e72c84396, key: PrivateKey("b46da29dea3ee64cf3664c345dd8e1bed782cca9d587cf5eb17b0b52464bcef3"), known_hosts: {"jcvitg.whlpej.zljie": [127.0.0.1, ::ffff:87.239.113.41, ::ffff:115.241.51.176, c35a:3aa1:eb11:a5b5:8619:3e9a:c901:db34, ::ffff:219.198.95.80], "xlqw.kmrje": [127.0.0.1], "jroor.tuylf": [::ffff:227.45.177.110, 84.159.66.9, 182.123.195.168], "wtlzbd.xqzq": [161.225.212.2, 215.122.229.43], "spkuio.rbbm.uznge": [::ffff:6.159.237.48, ::ffff:166.8.177.72, 107.200.82.124, 127.0.0.1], "fmgj.meu.qzxfac": [98.219.249.233], "bvd.muan.tzt": [196.215.184.87], "pgwfpy.jos": [208.113.155.18, ::ffff:218.26.85.128, 5ed7:e9d2:270b:32f6:d3ef:c596:b1ea:e1b4, 210.213.21.227, ::ffff:18.178.67.22], "edev.vnsq": [::ffff:148.116.182.172, 113.13.126.243, ::ffff:62.168.107.87], "eye.kzkstg": [18.95.222.165], "nbrik.fgxqmv": [127.0.0.1, 402a:746e:c873:3f0f:cc26:af15:7dbe:ef9], "poogly.eltfsg.nspolz": [::ffff:120.213.144.213], "qvry.qzm.dedlwv": [127.0.0.1, 4ef1:be01:aa22:e836:69d8:5c5:3da3:1303, 148.101.60.102], "rsm.mrtfm": [127.0.0.1, ::ffff:127.0.0.1, 3bf3:2833:c1e5:e83c:a7:df8e:ea3f:ef27, 43.152.18.47, 182.1.104.202]}, tunnel_ip4: 100.64.0.1, tunnel_ip6: fd00:2021:1111::, system_dns_resolvers: [], upstream_dns_resolvers: [], internet_resource: None, connected_internet_resources: false }, ip4: None, ip6: Some(2001:db80::50), default_port: 56423, latency: 55ms }, gateways: {2ae98600-98b3-31c0-4147-4259845bf99c: Host { inner: RefGateway { key: PrivateKey("2b00c11ec80ab31bc5562a1fb716b24a04f56a7323356931dbf90e349e0302a5") }, ip4: Some(203.0.113.65), ip6: Some(2001:db80::1d), default_port: 2920, latency: 143ms }, 5d7859c3-64a9-e31f-6c49-3b6f146cdd75: Host { inner: RefGateway { key: PrivateKey("678ac080974b9ad05daa5e6b6e54e40991524202e0472d382b36b3aad49811ff") }, ip4: Some(203.0.113.55), ip6: Some(2001:db80::4a), default_port: 4933, latency: 32ms }, 5ed1105d-d018-a297-381f-df413ffc3084: Host { inner: RefGateway { key: PrivateKey("4c14cafd007561f2a03c2980ad4d18e4569d9fc997fb8682c97e4a4e87f3abd1") }, ip4: Some(203.0.113.75), ip6: Some(2001:db80::24), default_port: 30205, latency: 12ms }, 7133f342-b285-8e6e-5561-94f2ae0ce87a: Host { inner: RefGateway { key: PrivateKey("d51894cf0d72af0c55d31f6035714512ec27dd02399cf8ad88c1e1efe246d7fc") }, ip4: Some(203.0.113.60), ip6: Some(2001:db80::56), default_port: 20102, latency: 81ms }, b5585630-ea18-a747-738e-8be18742760b: Host { inner: RefGateway { key: PrivateKey("e9bd635f398b7966458408449d5831684d595b7a4d6bcdca6f52a2e530e75524") }, ip4: Some(203.0.113.42), ip6: Some(2001:db80::12), default_port: 50449, latency: 149ms }, ba48d9d3-d97b-2eb6-01ed-bbe83c9539b7: Host { inner: RefGateway { key: PrivateKey("7b392515fb0e64ca5ddf4fc74e3b2525f8ea887a604a234ebf58eafe1d4a8cb1") }, ip4: Some(203.0.113.30), ip6: Some(2001:db80::2e), default_port: 25808, latency: 52ms }, ce891cf6-81b7-2ff3-80f1-1f181029a6ab: Host { inner: RefGateway { key: PrivateKey("894e06cea1189477c50a5106dbfc3ccabce2aa01cb6e1d4a90d054f6f1f22f6a") }, ip4: Some(203.0.113.94), ip6: Some(2001:db80::5d), default_port: 63862, latency: 158ms }, f31f800f-54bb-ac45-fed1-8563410864fc: Host { inner: RefGateway { key: PrivateKey("c7063177e13717ba7aac987ec2ea94383ce9932efc26a6ef546e933a8eea59e8") }, ip4: Some(203.0.113.1), ip6: Some(2001:db80::3b), default_port: 51573, latency: 29ms }}, relays: {69b4ffe2-2cfd-f7cf-6435-6e9ded200d6a: Host { inner: 5423086251555565693, ip4: Some(203.0.113.61), ip6: Some(2001:db80::43), default_port: 3478, latency: 14ms }}, dns_servers: {78EAE2BD02F70DA85B217BF2E512DC76: Host { inner: RefDns, ip4: Some(0.0.0.0), ip6: None, default_port: 53, latency: 11ms }, AE19C141EF292BB87FCEFD7060F4C83E: Host { inner: RefDns, ip4: Some(127.0.0.1), ip6: None, default_port: 53, latency: 19ms }, C4D39B871E6D0183C780C44D4992F0E1: Host { inner: RefDns, ip4: None, ip6: Some(::ffff:243.22.7.182), default_port: 53, latency: 28ms }}, portal: StubPortal { gateways_by_site: {afcc7b10-ccf7-9add-c7b5-3555e0be5e7e: {5d7859c3-64a9-e31f-6c49-3b6f146cdd75, 2ae98600-98b3-31c0-4147-4259845bf99c, b5585630-ea18-a747-738e-8be18742760b}, dcfbe3cb-d0fe-38bd-8421-ac52c742bc07: {7133f342-b285-8e6e-5561-94f2ae0ce87a, f31f800f-54bb-ac45-fed1-8563410864fc}, 52326a79-16e9-8420-672a-05a41081cb73: {ba48d9d3-d97b-2eb6-01ed-bbe83c9539b7, ce891cf6-81b7-2ff3-80f1-1f181029a6ab, 5ed1105d-d018-a297-381f-df413ffc3084}}, cidr_resources: {207a49a8-7fba-6069-a00d-f05eb9a5d535: ResourceDescriptionCidr { id: 207a49a8-7fba-6069-a00d-f05eb9a5d535, address: V6(Ipv6Network { network_address: ::ffff:127.0.0.0, netmask: 124 }), name: "ebexyx", address_description: Some("doiakr"), sites: [Site { id: dcfbe3cb-d0fe-38bd-8421-ac52c742bc07, name: "fkojz" }] }, 6e31d543-0fc4-1ec0-81b7-adf6f9b3c43d: ResourceDescriptionCidr { id: 6e31d543-0fc4-1ec0-81b7-adf6f9b3c43d, address: V6(Ipv6Network { network_address: ::ffff:168.12.32.132, netmask: 127 }), name: "rqxkruttci", address_description: Some("bobx"), sites: [Site { id: afcc7b10-ccf7-9add-c7b5-3555e0be5e7e, name: "mxmvrsaj" }] }, fc68f7da-804a-eaa0-cb54-b84dde1e89cc: ResourceDescriptionCidr { id: fc68f7da-804a-eaa0-cb54-b84dde1e89cc, address: V4(Ipv4Network { network_address: 157.223.39.255, netmask: 32 }), name: "syffivavzf", address_description: Some("hgkpgljneh"), sites: [Site { id: 52326a79-16e9-8420-672a-05a41081cb73, name: "xvmdsrrhv" }] }}, dns_resources: {099ae5da-e893-e8d0-8255-dbc3dcab2d64: ResourceDescriptionDns { id: 099ae5da-e893-e8d0-8255-dbc3dcab2d64, address: "*.anqgwm.zaixud.hgsrgh", name: "xupszi", address_description: Some("xbtb"), sites: [Site { id: dcfbe3cb-d0fe-38bd-8421-ac52c742bc07, name: "fkojz" }] }, bcf6e567-d34a-0974-414a-d81d2a784b38: ResourceDescriptionDns { id: bcf6e567-d34a-0974-414a-d81d2a784b38, address: "*.stw.dms.jxowkf", name: "dmwemcvb", address_description: Some("zyktynzt"), sites: [Site { id: dcfbe3cb-d0fe-38bd-8421-ac52c742bc07, name: "fkojz" }] }, a6cef25d-7c9b-10be-c1e2-8c8768b91a82: ResourceDescriptionDns { id: a6cef25d-7c9b-10be-c1e2-8c8768b91a82, address: "*.lkc.xvjc.qbyct", name: "xngldtyv", address_description: Some("xjwhxsso"), sites: [Site { id: dcfbe3cb-d0fe-38bd-8421-ac52c742bc07, name: "fkojz" }] }, 2f0bfb28-e584-2ce6-3d14-e807b4b4e2c6: ResourceDescriptionDns { id: 2f0bfb28-e584-2ce6-3d14-e807b4b4e2c6, address: "*.scwvgv.zgukyq", name: "quaf", address_description: Some("sestdmgp"), sites: [Site { id: afcc7b10-ccf7-9add-c7b5-3555e0be5e7e, name: "mxmvrsaj" }] }}, internet_resource: ResourceDescriptionInternet { id: 4f31b98b-1bf0-5a89-7071-c27e004ff434, sites: [Site { id: afcc7b10-ccf7-9add-c7b5-3555e0be5e7e, name: "mxmvrsaj" }] } }, drop_direct_client_traffic: true, global_dns_records: {Name(nxano.anqgwm.zaixud.hgsrgh.): {2001:db80::7f, 2001:db80::95, 2001:db80::52}, Name(ouyhdo.adklj.stw.dms.jxowkf.): {2001:db80::11}, Name(ubcw.stw.dms.jxowkf.): {2001:db80::e0, 2001:db80::98, 2001:db80::d1}, Name(urdg.stw.dms.jxowkf.): {2001:db80::a}, Name(hqyhg.nfi.): {::ffff:127.0.0.1, ::ffff:214.191.54.144, 251.54.106.18, 141.125.54.18}, Name(syvrsu.cnb.lkc.xvjc.qbyct.): {2001:db80::e1, 198.51.100.111, 2001:db80::c7, 2001:db80::46, 198.51.100.230}, Name(wybkgg.qisco.lkc.xvjc.qbyct.): {198.51.100.80, 198.51.100.231}, Name(rrnurp.lkc.xvjc.qbyct.): {198.51.100.48, 2001:db80::2d, 198.51.100.45, 2001:db80::af, 198.51.100.250}, Name(jipha.trhm.): {::ffff:127.0.0.1, 127.0.0.1, 89.131.140.177}, Name(epd.ziazts.scwvgv.zgukyq.): {2001:db80::55}}, network: RoutingTable { routes: {(V4(Ipv4Network { network_address: 0.0.0.0, netmask: 32 }), DnsServer(78EAE2BD02F70DA85B217BF2E512DC76)), (V4(Ipv4Network { network_address: 127.0.0.1, netmask: 32 }), DnsServer(AE19C141EF292BB87FCEFD7060F4C83E)), (V4(Ipv4Network { network_address: 203.0.113.1, netmask: 32 }), Gateway(f31f800f-54bb-ac45-fed1-8563410864fc)), (V4(Ipv4Network { network_address: 203.0.113.30, netmask: 32 }), Gateway(ba48d9d3-d97b-2eb6-01ed-bbe83c9539b7)), (V4(Ipv4Network { network_address: 203.0.113.42, netmask: 32 }), Gateway(b5585630-ea18-a747-738e-8be18742760b)), (V4(Ipv4Network { network_address: 203.0.113.55, netmask: 32 }), Gateway(5d7859c3-64a9-e31f-6c49-3b6f146cdd75)), (V4(Ipv4Network { network_address: 203.0.113.60, netmask: 32 }), Gateway(7133f342-b285-8e6e-5561-94f2ae0ce87a)), (V4(Ipv4Network { network_address: 203.0.113.61, netmask: 32 }), Relay(69b4ffe2-2cfd-f7cf-6435-6e9ded200d6a)), (V4(Ipv4Network { network_address: 203.0.113.65, netmask: 32 }), Gateway(2ae98600-98b3-31c0-4147-4259845bf99c)), (V4(Ipv4Network { network_address: 203.0.113.75, netmask: 32 }), Gateway(5ed1105d-d018-a297-381f-df413ffc3084)), (V4(Ipv4Network { network_address: 203.0.113.94, netmask: 32 }), Gateway(ce891cf6-81b7-2ff3-80f1-1f181029a6ab)), (V6(Ipv6Network { network_address: ::ffff:243.22.7.182, netmask: 128 }), DnsServer(C4D39B871E6D0183C780C44D4992F0E1)), (V6(Ipv6Network { network_address: 2001:db80::12, netmask: 128 }), Gateway(b5585630-ea18-a747-738e-8be18742760b)), (V6(Ipv6Network { network_address: 2001:db80::1d, netmask: 128 }), Gateway(2ae98600-98b3-31c0-4147-4259845bf99c)), (V6(Ipv6Network { network_address: 2001:db80::24, netmask: 128 }), Gateway(5ed1105d-d018-a297-381f-df413ffc3084)), (V6(Ipv6Network { network_address: 2001:db80::2e, netmask: 128 }), Gateway(ba48d9d3-d97b-2eb6-01ed-bbe83c9539b7)), (V6(Ipv6Network { network_address: 2001:db80::3b, netmask: 128 }), Gateway(f31f800f-54bb-ac45-fed1-8563410864fc)), (V6(Ipv6Network { network_address: 2001:db80::43, netmask: 128 }), Relay(69b4ffe2-2cfd-f7cf-6435-6e9ded200d6a)), (V6(Ipv6Network { network_address: 2001:db80::4a, netmask: 128 }), Gateway(5d7859c3-64a9-e31f-6c49-3b6f146cdd75)), (V6(Ipv6Network { network_address: 2001:db80::50, netmask: 128 }), Client(dc8a73db-d5f0-8f52-1788-0c5e72c84396)), (V6(Ipv6Network { network_address: 2001:db80::56, netmask: 128 }), Gateway(7133f342-b285-8e6e-5561-94f2ae0ce87a)), (V6(Ipv6Network { network_address: 2001:db80::5d, netmask: 128 }), Gateway(ce891cf6-81b7-2ff3-80f1-1f181029a6ab))} } }, [ActivateResource(Cidr(ResourceDescriptionCidr { id: 6e31d543-0fc4-1ec0-81b7-adf6f9b3c43d, address: V6(Ipv6Network { network_address: ::ffff:168.12.32.132, netmask: 127 }), name: "rqxkruttci", address_description: Some("bobx"), sites: [Site { id: afcc7b10-ccf7-9add-c7b5-3555e0be5e7e, name: "mxmvrsaj" }] })), SendICMPPacketToCidrResource { src: fd00:2021:1111::, dst: ::ffff:168.12.32.132, seq: 0, identifier: 0 }, UpdateSystemDnsServers([]), UpdateSystemDnsServers([]), Idle, Idle, SendICMPPacketToCidrResource { src: fd00:2021:1111::, dst: ::ffff:168.12.32.132, seq: 0, identifier: 0 }, SendICMPPacketToCidrResource { src: fd00:2021:1111::, dst: ::ffff:168.12.32.132, seq: 87, identifier: 11969 }], None) +cc 765a7d0f7ee1179659c3322f274497c76a85bb35ce8af707ece2c6e2ab73008c # shrinks to (initial_state, transitions, seen_counter) = (ReferenceState { client: Host { inner: RefClient { id: b0013643-b9d2-9301-6f6d-97f71ac3e04e, key: PrivateKey("cfe38de0079228b74d9ae22deaee497ee4b290d1588aaa1eb12a826dcb9ed931"), known_hosts: {}, tunnel_ip4: 100.64.0.1, tunnel_ip6: fd00:2021:1111::, system_dns_resolvers: [], upstream_dns_resolvers: [], internet_resource: None, connected_internet_resources: false }, ip4: None, ip6: Some(2001:db80::3e), default_port: 24435, latency: 126ms }, gateways: {8e829924-a9e8-c7a6-c486-49d64544746a: Host { inner: RefGateway { key: PrivateKey("7f2b6ff891abc2f854afc649c3698dedf1f27a5732fc2d79467e78eb770b7749") }, ip4: Some(203.0.113.96), ip6: Some(2001:db80::49), default_port: 60796, latency: 34ms }, 8f11b5b5-045b-16c8-b7f4-691adfc35d60: Host { inner: RefGateway { key: PrivateKey("7d0cb5ddbd24b97b80e447b766b6c9c1fe45751716fc4625223cb9e9133f4756") }, ip4: Some(203.0.113.89), ip6: Some(2001:db80::56), default_port: 19224, latency: 47ms }, d6d448e7-2e3c-1a35-7719-9d529984530a: Host { inner: RefGateway { key: PrivateKey("e04218c6cede8bf4864aa00763681912b0ad8128393861019108e69baa1c7aac") }, ip4: Some(203.0.113.33), ip6: Some(2001:db80::59), default_port: 22948, latency: 199ms }}, relays: {e4f1edb2-6100-7485-aeff-4b69ac52b872: Host { inner: 5144841934641988632, ip4: Some(203.0.113.70), ip6: Some(2001:db80::24), default_port: 3478, latency: 42ms }, ea585ddd-373f-4174-bd49-ce671c71ca15: Host { inner: 6037351235317311124, ip4: Some(203.0.113.77), ip6: Some(2001:db80::54), default_port: 3478, latency: 47ms }}, dns_servers: {6E27E8090FB3EAF6A8F39B8F9D51FF9: Host { inner: RefDns, ip4: None, ip6: Some(8884:1279:1331:6e05:9d2c:7421:8393:1790), default_port: 53, latency: 20ms }, 6418C5B5493D31FCBAE2BCEC3A2D7034: Host { inner: RefDns, ip4: Some(205.22.71.96), ip6: None, default_port: 53, latency: 30ms }, AF1A614FDAF0AD32E7DE874F6ABF883A: Host { inner: RefDns, ip4: None, ip6: Some(6043:da76:80ef:b3cf:5fe1:8cdb:31dd:ae0e), default_port: 53, latency: 21ms }, D4AA3A65399F8BF9E2243CF9033635E4: Host { inner: RefDns, ip4: Some(25.174.212.115), ip6: None, default_port: 53, latency: 17ms }}, portal: StubPortal { gateways_by_site: {9ab59e99-13dd-8f8c-3a1c-d90337402303: {d6d448e7-2e3c-1a35-7719-9d529984530a, 8e829924-a9e8-c7a6-c486-49d64544746a, 8f11b5b5-045b-16c8-b7f4-691adfc35d60}}, cidr_resources: {c1401505-530f-3941-d0e5-c112d366aa32: ResourceDescriptionCidr { id: c1401505-530f-3941-d0e5-c112d366aa32, address: V4(Ipv4Network { network_address: 127.0.0.0, netmask: 27 }), name: "snbaky", address_description: Some("bzglvvh"), sites: [Site { id: 9ab59e99-13dd-8f8c-3a1c-d90337402303, name: "owvkfp" }] }}, dns_resources: {6776fa66-6389-e243-74f1-256c3b11be8f: ResourceDescriptionDns { id: 6776fa66-6389-e243-74f1-256c3b11be8f, address: "lpe.tjoq.xniyks", name: "lltnzab", address_description: Some("ijhpaumg"), sites: [Site { id: 9ab59e99-13dd-8f8c-3a1c-d90337402303, name: "owvkfp" }] }, 586b819c-069a-5f16-a473-03c905be76e1: ResourceDescriptionDns { id: 586b819c-069a-5f16-a473-03c905be76e1, address: "dddh.gtlbku", name: "bdvbnoqg", address_description: None, sites: [Site { id: 9ab59e99-13dd-8f8c-3a1c-d90337402303, name: "owvkfp" }] }, e1463904-e7a5-b070-8285-a3309b4271d0: ResourceDescriptionDns { id: e1463904-e7a5-b070-8285-a3309b4271d0, address: "**.swe.sdwq", name: "ugkqx", address_description: None, sites: [Site { id: 9ab59e99-13dd-8f8c-3a1c-d90337402303, name: "owvkfp" }] }, e6e8b5ff-1ba8-eabe-4178-73c9bd4e210f: ResourceDescriptionDns { id: e6e8b5ff-1ba8-eabe-4178-73c9bd4e210f, address: "iaqax.acayd.ylvzk", name: "cdjgwg", address_description: Some("pwmsj"), sites: [Site { id: 9ab59e99-13dd-8f8c-3a1c-d90337402303, name: "owvkfp" }] }}, internet_resource: ResourceDescriptionInternet { id: 1c841ad0-facf-3e0f-cd88-8b86db55ec69, sites: [Site { id: 9ab59e99-13dd-8f8c-3a1c-d90337402303, name: "owvkfp" }] } }, drop_direct_client_traffic: false, global_dns_records: {Name(kulvm.daalmj.gnvhip.): {65.92.187.10, bcaa:7b4c:9335:c5:659a:f690:2f55:338f, 248.255.11.236, 184.213.76.223}, Name(dddh.gtlbku.): {198.51.100.98, 2001:db80::d8, 198.51.100.157, 198.51.100.117, 2001:db80::22}, Name(lmb.swe.sdwq.): {2001:db80::5c, 198.51.100.196}, Name(lpe.tjoq.xniyks.): {198.51.100.246}, Name(iaqax.acayd.ylvzk.): {2001:db80::43, 2001:db80::1, 2001:db80::2}}, network: RoutingTable { routes: {(V4(Ipv4Network { network_address: 25.174.212.115, netmask: 32 }), DnsServer(D4AA3A65399F8BF9E2243CF9033635E4)), (V4(Ipv4Network { network_address: 203.0.113.33, netmask: 32 }), Gateway(d6d448e7-2e3c-1a35-7719-9d529984530a)), (V4(Ipv4Network { network_address: 203.0.113.70, netmask: 32 }), Relay(e4f1edb2-6100-7485-aeff-4b69ac52b872)), (V4(Ipv4Network { network_address: 203.0.113.77, netmask: 32 }), Relay(ea585ddd-373f-4174-bd49-ce671c71ca15)), (V4(Ipv4Network { network_address: 203.0.113.89, netmask: 32 }), Gateway(8f11b5b5-045b-16c8-b7f4-691adfc35d60)), (V4(Ipv4Network { network_address: 203.0.113.96, netmask: 32 }), Gateway(8e829924-a9e8-c7a6-c486-49d64544746a)), (V4(Ipv4Network { network_address: 205.22.71.96, netmask: 32 }), DnsServer(6418C5B5493D31FCBAE2BCEC3A2D7034)), (V6(Ipv6Network { network_address: 2001:db80::24, netmask: 128 }), Relay(e4f1edb2-6100-7485-aeff-4b69ac52b872)), (V6(Ipv6Network { network_address: 2001:db80::3e, netmask: 128 }), Client(b0013643-b9d2-9301-6f6d-97f71ac3e04e)), (V6(Ipv6Network { network_address: 2001:db80::49, netmask: 128 }), Gateway(8e829924-a9e8-c7a6-c486-49d64544746a)), (V6(Ipv6Network { network_address: 2001:db80::54, netmask: 128 }), Relay(ea585ddd-373f-4174-bd49-ce671c71ca15)), (V6(Ipv6Network { network_address: 2001:db80::56, netmask: 128 }), Gateway(8f11b5b5-045b-16c8-b7f4-691adfc35d60)), (V6(Ipv6Network { network_address: 2001:db80::59, netmask: 128 }), Gateway(d6d448e7-2e3c-1a35-7719-9d529984530a)), (V6(Ipv6Network { network_address: 6043:da76:80ef:b3cf:5fe1:8cdb:31dd:ae0e, netmask: 128 }), DnsServer(AF1A614FDAF0AD32E7DE874F6ABF883A)), (V6(Ipv6Network { network_address: 8884:1279:1331:6e05:9d2c:7421:8393:1790, netmask: 128 }), DnsServer(6E27E8090FB3EAF6A8F39B8F9D51FF9))} } }, [UpdateSystemDnsServers([6043:da76:80ef:b3cf:5fe1:8cdb:31dd:ae0e, 25.174.212.115]), SendDnsQueries([DnsQuery { domain: Name(lmb.swe.sdwq.), r_type: Rtype::AAAA, query_id: 541, dns_server: [6043:da76:80ef:b3cf:5fe1:8cdb:31dd:ae0e]:53 }]), ActivateResource(Dns(ResourceDescriptionDns { id: e1463904-e7a5-b070-8285-a3309b4271d0, address: "**.swe.sdwq", name: "ugkqx", address_description: None, sites: [Site { id: 9ab59e99-13dd-8f8c-3a1c-d90337402303, name: "owvkfp" }] })), SendICMPPacketToDnsResource { src: ::43:fc6e:731b:63c8, dst: Name(lmb.swe.sdwq.), seq: 0, identifier: 0 }, SendICMPPacketToDnsResource { src: fd00:2021:1111::, dst: Name(lmb.swe.sdwq.), seq: 40700, identifier: 45698 }], None) diff --git a/rust/connlib/tunnel/src/dns.rs b/rust/connlib/tunnel/src/dns.rs index 3d61e18a7..ac6c96891 100644 --- a/rust/connlib/tunnel/src/dns.rs +++ b/rust/connlib/tunnel/src/dns.rs @@ -1,7 +1,6 @@ use crate::client::IpProvider; use connlib_shared::messages::{DnsServer, ResourceId}; use connlib_shared::DomainName; -use domain::base::RelativeName; use domain::base::{ iana::{Class, Rcode, Rtype}, Message, MessageBuilder, ToName, @@ -10,6 +9,7 @@ use domain::rdata::AllRecordData; use ip_packet::IpPacket; use ip_packet::Packet as _; use itertools::Itertools; +use pattern::{Candidate, Pattern}; use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; @@ -23,8 +23,8 @@ pub struct StubResolver { fqdn_to_ips: HashMap>, ips_to_fqdn: HashMap, ip_provider: IpProvider, - /// All DNS resources we know about, indexed by their domain (could be wildcard domain like `*.mycompany.com`). - dns_resources: HashMap, + /// All DNS resources we know about, indexed by the glob pattern they match against. + dns_resources: HashMap, /// Fixed dns name that will be resolved to fixed ip addrs, similar to /etc/hosts known_hosts: KnownHosts, } @@ -112,8 +112,16 @@ impl StubResolver { Some((fqdn, self.fqdn_to_ips.get(fqdn).unwrap())) } - pub(crate) fn add_resource(&mut self, id: ResourceId, address: String) -> bool { - let existing = self.dns_resources.insert(address, id); + pub(crate) fn add_resource(&mut self, id: ResourceId, pattern: String) -> bool { + let parsed_pattern = match Pattern::new(&pattern) { + Ok(p) => p, + Err(e) => { + tracing::warn!(%pattern, "Domain pattern is not valid: {e}"); + return false; + } + }; + + let existing = self.dns_resources.insert(parsed_pattern, id); existing.is_none() } @@ -157,8 +165,26 @@ impl StubResolver { ips } - fn match_resource(&self, domain_name: &DomainName) -> Option { - match_domain(domain_name, &self.dns_resources) + /// Attempts to match the given domain against our list of possible patterns. + /// + /// This performs a linear search and is thus O(N) and **must not** be called in the hot-path of packet routing. + #[tracing::instrument(level = "trace", skip_all, fields(domain))] + fn match_resource_linear(&self, domain_name: &DomainName) -> Option { + let name = Candidate::from_domain(domain_name); + + for (pattern, id) in &self.dns_resources { + if pattern.matches(&name) { + tracing::trace!(%id, %pattern, "Matched domain"); + + return Some(*id); + } + + tracing::trace!(%pattern, %id, "No match"); + } + + tracing::debug!("No resources matched"); + + None } fn resource_address_name_by_reservse_dns( @@ -225,7 +251,8 @@ impl StubResolver { return Some(ResolveStrategy::LocalResponse(packet)); } - let maybe_resource = self.match_resource(&domain); + // `match_resource` is `O(N)` which we deem fine for DNS queries. + let maybe_resource = self.match_resource_linear(&domain); let resource_records = match (qtype, maybe_resource) { (_, Some(resource)) if !self.knows_resource(&resource) => { @@ -304,68 +331,17 @@ fn build_dns_with_answer( } pub fn is_subdomain(name: &DomainName, resource: &str) -> bool { - let question_mark = RelativeName::>::from_octets(b"\x01?".as_ref().into()).unwrap(); - let Ok(resource) = DomainName::vec_from_str(resource) else { - return false; + let pattern = match Pattern::new(resource) { + Ok(p) => p, + Err(e) => { + tracing::warn!(%resource, "Unable to parse pattern: {e}"); + return false; + } }; - if resource.starts_with(&question_mark) { - return resource - .parent() - .is_some_and(|r| r == name || name.parent().is_some_and(|n| r == n)); - } + let candidate = Candidate::from_domain(name); - if resource.starts_with(&RelativeName::wildcard_vec()) { - let Some(resource) = resource.parent() else { - return false; - }; - return name.iter_suffixes().any(|n| n == resource); - } - - name == &resource -} - -fn match_domain(name: &DomainName, resources: &HashMap) -> Option -where - T: Copy, -{ - // Safety: `?` is less than 254 bytes long. - const QUESTION_MARK: RelativeName<&'static [u8]> = - unsafe { RelativeName::from_octets_unchecked(b"\x01?") }; - // Safety: `*` is less than 254 bytes long. - const WILDCARD: RelativeName<&'static [u8]> = - unsafe { RelativeName::from_octets_unchecked(b"\x01*") }; - - // First, check for full match. - if let Some(resource) = resources.get(&name.to_string()) { - return Some(*resource); - } - - // Second, check for `?` matching this domain exactly. - let qm_dot_domain = QUESTION_MARK.chain(name).ok()?.to_string(); - if let Some(resource) = resources.get(&qm_dot_domain) { - return Some(*resource); - } - - // Third, check for `?` matching up to 1 parent. - if let Some(parent) = name.parent() { - let qm_dot_parent = QUESTION_MARK.chain(parent).ok()?.to_string(); - - if let Some(resource) = resources.get(&qm_dot_parent) { - return Some(*resource); - } - } - - // Last, check for any wildcard domains, starting with the most specific one. - for suffix in name.iter_suffixes() { - let wildcard_dot_suffix = WILDCARD.chain(suffix).ok()?.to_string(); - - if let Some(resource) = resources.get(&wildcard_dot_suffix) { - return Some(*resource); - } - } - - None + pattern.matches(&candidate) } fn reverse_dns_addr(name: &str) -> Option { @@ -438,9 +414,78 @@ fn ips_to_fqdn_for_known_hosts( .collect() } +mod pattern { + use super::*; + use std::{convert::Infallible, fmt, str::FromStr}; + + #[derive(Debug, PartialEq, Eq, Hash)] + pub struct Pattern { + inner: glob::Pattern, + original: String, + } + + impl fmt::Display for Pattern { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.original.fmt(f) + } + } + + impl Pattern { + pub fn new(p: &str) -> Result { + Ok(Self { + inner: glob::Pattern::new(&p.replace('.', "/"))?, + original: p.to_string(), + }) + } + + /// Matches a [`Candidate`] against this [`Pattern`]. + /// + /// Matching only requires a reference, thus allowing users to test a [`Candidate`] against multiple [`Pattern`]s. + pub fn matches(&self, domain: &Candidate) -> bool { + let domain = domain.0.as_str(); + + if let Some(rem) = self.inner.as_str().strip_prefix("*/") { + if domain == rem { + return true; + } + } + + self.inner.matches_with( + domain, + glob::MatchOptions { + case_sensitive: false, + require_literal_separator: true, + require_literal_leading_dot: false, + }, + ) + } + } + + /// A candidate for matching against a domain [`Pattern`]. + /// + /// Creates a type-safe contract that replaces `.` with `/` in the domain which is requires for pattern matching. + pub struct Candidate(String); + + impl Candidate { + pub fn from_domain(domain: &DomainName) -> Self { + Self(domain.to_string().replace('.', "/")) + } + } + + impl FromStr for Candidate { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Self(s.replace('.', "/"))) + } + } +} + #[cfg(test)] mod tests { use super::*; + use std::str::FromStr as _; + use test_case::test_case; #[test] fn reverse_dns_addr_works_v4() { @@ -496,66 +541,82 @@ mod tests { } #[test] - fn wildcard_matching() { - let resources = HashMap::from([("*.foo.com".to_string(), 0), ("*.com".to_string(), 1)]); + fn pattern_displays_without_slash() { + let pattern = Pattern::new("**.example.com").unwrap(); - assert_eq!(match_domain(&domain("a.foo.com"), &resources), Some(0)); - assert_eq!(match_domain(&domain("foo.com"), &resources), Some(0)); - assert_eq!(match_domain(&domain("a.b.foo.com"), &resources), Some(0)); - assert_eq!(match_domain(&domain("oo.com"), &resources), Some(1)); - assert_eq!(match_domain(&domain("oo.xyz"), &resources), None); + assert_eq!(pattern.to_string(), "**.example.com") } - #[test] - fn question_mark_matching() { - let resources = HashMap::from([("?.bar.com".to_string(), 1)]); + #[test_case("**.example.com", "example.com"; "double star matches root domain")] + #[test_case("app.**.example.com", "app.bar.foo.example.com"; "double star matches multiple levels within domain")] + #[test_case("**.example.com", "foo.example.com"; "double star matches one level")] + #[test_case("**.example.com", "foo.bar.example.com"; "double star matches two levels")] + #[test_case("*.example.com", "foo.example.com"; "single star matches one level")] + #[test_case("*.example.com", "example.com"; "single star matches root domain")] + #[test_case("foo.*.example.com", "foo.bar.example.com"; "single star matches one domain within domain")] + #[test_case("app.*.*.example.com", "app.foo.bar.example.com"; "single star can appear on multiple levels")] + #[test_case("app.f??.example.com", "app.foo.example.com"; "question mark matches one letter")] + #[test_case("app.example.com", "app.example.com"; "matches literal domain")] + #[test_case("*?*.example.com", "app.example.com"; "mix of * and ?")] + #[test_case("app.**.web.**.example.com", "app.web.example.com"; "multiple double star within domain")] - assert_eq!(match_domain(&domain("a.bar.com"), &resources), Some(1)); - assert_eq!(match_domain(&domain("bar.com"), &resources), Some(1)); - assert_eq!(match_domain(&domain("a.b.bar.com"), &resources), None); + fn domain_pattern_matches(pattern: &str, domain: &str) { + let pattern = Pattern::new(pattern).unwrap(); + let candidate = Candidate::from_str(domain).unwrap(); + + let matches = pattern.matches(&candidate); + + assert!(matches); } - #[test] - fn exact_matching() { - let resources = HashMap::from([("baz.com".to_string(), 2)]); + #[test_case("app.*.example.com", "app.foo.bar.example.com"; "single star does not match two level")] + #[test_case("app.*com", "app.foo.com"; "single star does not match dot")] + #[test_case("app?com", "app.com"; "question mark does not match dot")] + fn domain_pattern_does_not_match(pattern: &str, domain: &str) { + let pattern = Pattern::new(pattern).unwrap(); + let candidate = Candidate::from_str(domain).unwrap(); - assert_eq!(match_domain(&domain("baz.com"), &resources), Some(2)); - assert_eq!(match_domain(&domain("a.baz.com"), &resources), None); - assert_eq!(match_domain(&domain("a.b.baz.com"), &resources), None); - } + let matches = pattern.matches(&candidate); - #[test] - fn exact_subdomain_match() { - assert!(is_subdomain(&domain("foo.com"), "foo.com")); - assert!(!is_subdomain(&domain("a.foo.com"), "foo.com")); - assert!(!is_subdomain(&domain("a.b.foo.com"), "foo.com")); - assert!(!is_subdomain(&domain("foo.com"), "a.foo.com")); - } - - #[test] - fn wildcard_subdomain_match() { - assert!(is_subdomain(&domain("foo.com"), "*.foo.com")); - assert!(is_subdomain(&domain("a.foo.com"), "*.foo.com")); - assert!(is_subdomain(&domain("a.foo.com"), "*.a.foo.com")); - assert!(is_subdomain(&domain("b.a.foo.com"), "*.a.foo.com")); - assert!(is_subdomain(&domain("a.b.foo.com"), "*.foo.com")); - assert!(!is_subdomain(&domain("afoo.com"), "*.foo.com")); - assert!(!is_subdomain(&domain("b.afoo.com"), "*.foo.com")); - assert!(!is_subdomain(&domain("bar.com"), "*.foo.com")); - assert!(!is_subdomain(&domain("foo.com"), "*.a.foo.com")); - } - - #[test] - fn question_mark_subdomain_match() { - assert!(is_subdomain(&domain("foo.com"), "?.foo.com")); - assert!(is_subdomain(&domain("a.foo.com"), "?.foo.com")); - assert!(!is_subdomain(&domain("a.b.foo.com"), "?.foo.com")); - assert!(!is_subdomain(&domain("bar.com"), "?.foo.com")); - assert!(!is_subdomain(&domain("foo.com"), "?.a.foo.com")); - assert!(!is_subdomain(&domain("afoo.com"), "?.foo.com")); - } - - fn domain(name: &str) -> DomainName { - DomainName::vec_from_str(name).unwrap() + assert!(!matches); + } +} + +#[cfg(feature = "divan")] +mod benches { + use super::*; + use rand::{distributions::DistString, seq::IteratorRandom, Rng}; + + #[divan::bench( + consts = [10, 100, 1_000, 10_000, 100_000] + )] + fn match_domain_linear(bencher: divan::Bencher) { + bencher + .with_inputs(|| { + let mut resolver = StubResolver::new(HashMap::default()); + let mut rng = rand::thread_rng(); + + for n in 0..NUM_RES { + resolver.add_resource(ResourceId::from_u128(n), make_domain(&mut rng)); + } + + let needle = resolver + .dns_resources + .keys() + .choose(&mut rng) + .unwrap() + .to_string(); + + let needle = DomainName::vec_from_str(&needle).unwrap(); + + (resolver, needle) + }) + .bench_refs(|(resolver, needle)| resolver.match_resource_linear(needle).unwrap()); + } + + fn make_domain(rng: &mut impl Rng) -> String { + (0..rng.gen_range(2..5)) + .map(|_| rand::distributions::Alphanumeric.sample_string(rng, 3)) + .join(".") } } diff --git a/rust/connlib/tunnel/src/tests/sim_client.rs b/rust/connlib/tunnel/src/tests/sim_client.rs index ff9b4fef8..c3824acdd 100644 --- a/rust/connlib/tunnel/src/tests/sim_client.rs +++ b/rust/connlib/tunnel/src/tests/sim_client.rs @@ -674,13 +674,7 @@ fn is_subdomain(name: &str, record: &str) -> bool { return false; }; match first { - "*" => name.ends_with(end) && name.strip_suffix(end).is_some_and(|n| n.ends_with('.')), - "?" => { - name.ends_with(end) - && name - .strip_suffix(end) - .is_some_and(|n| n.ends_with('.') && n.matches('.').count() == 1) - } + "**" => name.ends_with(end) && name.strip_suffix(end).is_some_and(|n| n.ends_with('.')), _ => false, } } diff --git a/rust/connlib/tunnel/src/tests/strategies.rs b/rust/connlib/tunnel/src/tests/strategies.rs index 93431c308..fe04b479c 100644 --- a/rust/connlib/tunnel/src/tests/strategies.rs +++ b/rust/connlib/tunnel/src/tests/strategies.rs @@ -66,7 +66,7 @@ pub(crate) fn stub_portal() -> impl Strategy { prop_oneof![ non_wildcard_dns_resource(any_site(sites.clone())), star_wildcard_dns_resource(any_site(sites.clone())), - question_mark_wildcard_dns_resource(any_site(sites.clone())), + double_star_wildcard_dns_resource(any_site(sites.clone())), ], 1..5, ); @@ -188,11 +188,11 @@ fn star_wildcard_dns_resource( }) } -fn question_mark_wildcard_dns_resource( +fn double_star_wildcard_dns_resource( site: impl Strategy, ) -> impl Strategy { dns_resource(site.prop_map(|s| vec![s])).prop_map(|r| ResourceDescriptionDns { - address: format!("?.{}", r.address), + address: format!("**.{}", r.address), ..r }) } diff --git a/rust/connlib/tunnel/src/tests/stub_portal.rs b/rust/connlib/tunnel/src/tests/stub_portal.rs index e36b1a1aa..155049900 100644 --- a/rust/connlib/tunnel/src/tests/stub_portal.rs +++ b/rust/connlib/tunnel/src/tests/stub_portal.rs @@ -211,17 +211,14 @@ impl StubPortal { .map(|resource| { let address = resource.address.clone(); - match address.chars().next().unwrap() { - '*' => subdomain_records( - address.trim_start_matches("*.").to_owned(), - domain_name(1..3), - ) - .boxed(), - '?' => subdomain_records( - address.trim_start_matches("?.").to_owned(), - domain_label(), - ) - .boxed(), + // Only generate simple wildcard domains for these tests. + // The matching logic is extensively unit-tested so we don't need to cover all cases here. + // What we do want to cover is multiple domains pointing to the same resource. + // For example, `*.example.com` and `app.example.com`. + match address.split_once('.') { + Some(("*" | "**", base)) => { + subdomain_records(base.to_owned(), domain_label()).boxed() + } _ => resolved_ips() .prop_map(move |resolved_ips| { HashMap::from([(address.parse().unwrap(), resolved_ips)]) diff --git a/rust/gateway/Cargo.toml b/rust/gateway/Cargo.toml index 720989303..308b24a0e 100644 --- a/rust/gateway/Cargo.toml +++ b/rust/gateway/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "firezone-gateway" # mark:next-gateway-version -version = "1.1.6" +version = "1.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/rust/gui-client/src-tauri/Cargo.toml b/rust/gui-client/src-tauri/Cargo.toml index dbf1073bf..484c7b4bd 100644 --- a/rust/gui-client/src-tauri/Cargo.toml +++ b/rust/gui-client/src-tauri/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "firezone-gui-client" # mark:next-gui-version -version = "1.1.13" +version = "1.2.0" description = "Firezone" edition = "2021" default-run = "firezone-gui-client" diff --git a/rust/headless-client/Cargo.toml b/rust/headless-client/Cargo.toml index 531e0db6b..7ef36545b 100644 --- a/rust/headless-client/Cargo.toml +++ b/rust/headless-client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "firezone-headless-client" # mark:next-headless-version -version = "1.1.8" +version = "1.2.0" edition = "2021" authors = ["Firezone, Inc."] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/scripts/Makefile b/scripts/Makefile index 6e6522cdf..58e95ae50 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -20,11 +20,11 @@ current-gui-version = 1.1.12 current-headless-version = 1.1.7 # Tracks the next version to release for each platform -next-apple-version = 1.1.6 -next-android-version = 1.1.7 -next-gateway-version = 1.1.6 -next-gui-version = 1.1.13 -next-headless-version = 1.1.8 +next-apple-version = 1.2.0 +next-android-version = 1.2.0 +next-gateway-version = 1.2.0 +next-gui-version = 1.2.0 +next-headless-version = 1.2.0 # macOS uses a slightly different sed syntax ifeq ($(shell uname),Darwin) diff --git a/swift/apple/Firezone.xcodeproj/project.pbxproj b/swift/apple/Firezone.xcodeproj/project.pbxproj index e4333903c..7d19d97d3 100644 --- a/swift/apple/Firezone.xcodeproj/project.pbxproj +++ b/swift/apple/Firezone.xcodeproj/project.pbxproj @@ -585,7 +585,7 @@ ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(CONNLIB_TARGET_DIR)/aarch64-apple-ios/debug"; MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; - MARKETING_VERSION = 1.1.6; + MARKETING_VERSION = 1.2.0; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-lconnlib"; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).debug.network-extension"; @@ -627,7 +627,7 @@ ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(CONNLIB_TARGET_DIR)/aarch64-apple-ios/release"; MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; - MARKETING_VERSION = 1.1.6; + MARKETING_VERSION = 1.2.0; OTHER_LDFLAGS = "-lconnlib"; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).network-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -671,7 +671,7 @@ "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(CONNLIB_TARGET_DIR)/aarch64-apple-darwin/debug"; "LIBRARY_SEARCH_PATHS[arch=arm64e]" = "$(CONNLIB_TARGET_DIR)/aarch64-apple-darwin/debug"; "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(CONNLIB_TARGET_DIR)/x86_64-apple-darwin/debug"; - MARKETING_VERSION = 1.1.6; + MARKETING_VERSION = 1.2.0; OTHER_LDFLAGS = "-lconnlib"; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).debug.network-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -714,7 +714,7 @@ "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(CONNLIB_TARGET_DIR)/aarch64-apple-darwin/release"; "LIBRARY_SEARCH_PATHS[arch=arm64e]" = "$(CONNLIB_TARGET_DIR)/aarch64-apple-darwin/release"; "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(CONNLIB_TARGET_DIR)/x86_64-apple-darwin/release"; - MARKETING_VERSION = 1.1.6; + MARKETING_VERSION = 1.2.0; OTHER_LDFLAGS = "-lconnlib"; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).network-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -908,7 +908,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MARKETING_VERSION = 1.1.6; + MARKETING_VERSION = 1.2.0; OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -959,7 +959,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MARKETING_VERSION = 1.1.6; + MARKETING_VERSION = 1.2.0; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited)"; PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/website/src/components/Changelog/Android.tsx b/website/src/components/Changelog/Android.tsx index 3532e7e4e..464a9428f 100644 --- a/website/src/components/Changelog/Android.tsx +++ b/website/src/components/Changelog/Android.tsx @@ -10,8 +10,11 @@ export default function Android() { title="Android" > {/* - +
    + + Implements glob-like matching of domains for DNS resources. +
*/} diff --git a/website/src/components/Changelog/Apple.tsx b/website/src/components/Changelog/Apple.tsx index 6bf3860af..72a2415c6 100644 --- a/website/src/components/Changelog/Apple.tsx +++ b/website/src/components/Changelog/Apple.tsx @@ -9,8 +9,11 @@ export default function Apple() { href="https://apps.apple.com/us/app/firezone/id6443661826" title="macOS / iOS" > - {/* + {/*
    + + Implements glob-like matching of domains for DNS resources. +
*/} diff --git a/website/src/components/Changelog/GUI.tsx b/website/src/components/Changelog/GUI.tsx index 3416035e4..c67729efc 100644 --- a/website/src/components/Changelog/GUI.tsx +++ b/website/src/components/Changelog/GUI.tsx @@ -14,16 +14,10 @@ export default function GUI({ title }: { title: string }) { {/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This cannot be done when the issue's PR merges. */} {/* - +
    - - Fixes a bug where clearing the log files would delete the current files, preventing logs from being written. - - - Fixes a bug where relayed connections failed to establish after an idle period. - - - Fixes a bug where restrictive NATs caused connectivity problems. + + Implements glob-like matching of domains for DNS resources. Fixes a bug where the "Clear Logs" button did not clear the IPC service logs. diff --git a/website/src/components/Changelog/Gateway.tsx b/website/src/components/Changelog/Gateway.tsx index 48e8dd1e9..5ba9a3a10 100644 --- a/website/src/components/Changelog/Gateway.tsx +++ b/website/src/components/Changelog/Gateway.tsx @@ -10,13 +10,10 @@ export default function Gateway() { return ( {/* - +
      - - Fixes a bug where relayed connections failed to establish after an idle period. - - - Fixes a bug where restrictive NATs caused connectivity problems. + + Implements glob-like matching of domains for DNS resources.
    diff --git a/website/src/components/Changelog/Headless.tsx b/website/src/components/Changelog/Headless.tsx index 37e313506..a5a97e6e0 100644 --- a/website/src/components/Changelog/Headless.tsx +++ b/website/src/components/Changelog/Headless.tsx @@ -10,8 +10,11 @@ export default function Headless() { return ( {/* - +
      + + Implements glob-like matching of domains for DNS resources. +
    */}