pax_global_header00006660000000000000000000000064147461725510014526gustar00rootroot0000000000000052 comment=5f3dc9ef06f1752e94d0f564c64ad4e0ca24962a local-agent-rs-1.4.3/000077500000000000000000000000001474617255100143435ustar00rootroot00000000000000local-agent-rs-1.4.3/.gitignore000066400000000000000000000001151474617255100163300ustar00rootroot00000000000000target .idea **/*.deb **/*.changes **/*.buildinfo *.pyc **/venv tests/*.json local-agent-rs-1.4.3/.gitlab-ci.yml000066400000000000000000000001651474617255100170010ustar00rootroot00000000000000include: - project: 'ProtonVPN/Linux/integration/ci-libraries-rust' ref: develop file: 'develop-pipeline.yml' local-agent-rs-1.4.3/.gitmodules000066400000000000000000000003751474617255100165250ustar00rootroot00000000000000[submodule "python-proton-vpn-local-agent/scripts/devtools"] path = python-proton-vpn-local-agent/scripts/devtools url = ../integration/devtools.git [submodule "ci-libraries-rust"] path = ci-libraries-rust url = ../integration/ci-libraries-rust.git local-agent-rs-1.4.3/CODEOWNERS000066400000000000000000000003361474617255100157400ustar00rootroot00000000000000# ownership: loose * @ProtonVPN/groups/linux-developers /local_agent_rs/ @ProtonVPN/groups/linux-developers /python-proton-vpn-local-agent/ @ProtonVPN/groups/linux-developers /tests/ @ProtonVPN/groups/linux-developers local-agent-rs-1.4.3/README.md000066400000000000000000000016421474617255100156250ustar00rootroot00000000000000# What is this? This repo contains a rust crate for communicating with a proton LocalAgent, server, and python bindings for that crate. ## From github > cd python-proton-vpn-local-agent > cargo build --release > mv target/release/libpython_proton_vpn_local_agent.so local_agent.so The local_agent.so file is your python module. ## Internally on x86 From the root. > ci-libraries-rust/scripts/build-python-extension x86_64-unknown-linux-gnu && ci-libraries-rust/scripts/build-wheel That builds the python module. Install local_agent to your venv with > pip install python-proton-vpn-local-agent/target/*.whl Now you're away. ```python import proton.vpn.local_agent as local_agent connection = local_agent.AgentConnector().connect("localhost", key, certificate) ``` # Repo structure. The repo is split into 2 projects, these are: - local_agent_rs : The rust library - python-proton-vpn-local-agent : The python bindings local-agent-rs-1.4.3/ci-libraries-rust/000077500000000000000000000000001474617255100177035ustar00rootroot00000000000000local-agent-rs-1.4.3/local_agent_rs/000077500000000000000000000000001474617255100173175ustar00rootroot00000000000000local-agent-rs-1.4.3/local_agent_rs/Cargo.lock000066400000000000000000001031341474617255100212260ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[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", ] [[package]] name = "anstyle-wincon" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "asn1-rs" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", "nom", "num-traits", "rusticata-macros", "thiserror", "time", ] [[package]] name = "asn1-rs-derive" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "asn1-rs-impl" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "async-trait" version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "aws-lc-rs" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae74d9bd0a7530e8afd1770739ad34b36838829d6ad61818f9230f683f5ad77" dependencies = [ "aws-lc-sys", "mirai-annotations", "paste", "zeroize", ] [[package]] name = "aws-lc-sys" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e89b6941c2d1a7045538884d6e760ccfffdf8e1ffc2613d8efa74305e1f3752" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", "libc", "paste", ] [[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", "object", "rustc-demangle", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ "serde", ] [[package]] name = "bindgen" version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools", "lazy_static", "lazycell", "log", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", "which", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bytes" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" 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-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "cmake" version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ "cc", ] [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "data-encoding" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "der-parser" version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ "asn1-rs", "displaydoc", "nom", "num-bigint", "num-traits", "rusticata-macros", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "dunce" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_filter" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", "env_filter", "humantime", "log", ] [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" 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 = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ "windows-sys", ] [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[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 = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets", ] [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-agent-rs" version = "0.10.0" dependencies = [ "async-trait", "bincode", "env_logger", "log", "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "socket2", "static_assertions", "test-log", "thiserror", "tokio", "tokio-rustls", "webpki", "webpki-roots", "x509-parser", ] [[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 = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ "hermit-abi", "libc", "wasi", "windows-sys", ] [[package]] name = "mirai-annotations" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" [[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-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] [[package]] name = "oid-registry" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" dependencies = [ "asn1-rs", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[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", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "prettyplease" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "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.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[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", ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rusticata-macros" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ "nom", ] [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rustls" version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "aws-lc-rs", "log", "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", "serde", ] [[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 = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "test-log" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" dependencies = [ "env_logger", "test-log-macros", "tracing-subscriber", ] [[package]] name = "test-log-macros" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", "syn", ] [[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", ] [[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.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tokio" version = "1.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" dependencies = [ "backtrace", "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys", ] [[package]] name = "tokio-macros" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", "tokio", ] [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", ] [[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-subscriber" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "webpki" version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ "ring", "untrusted", ] [[package]] name = "webpki-roots" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" 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", "home", "once_cell", "rustix", ] [[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-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" 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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "x509-parser" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ "asn1-rs", "data-encoding", "der-parser", "lazy_static", "nom", "oid-registry", "rusticata-macros", "thiserror", "time", ] [[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", ] local-agent-rs-1.4.3/local_agent_rs/Cargo.toml000066400000000000000000000012371474617255100212520ustar00rootroot00000000000000[package] name = "local-agent-rs" version = "0.10.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] tokio-rustls = { version="0.26.0", features = ["tls12"] } tokio = { version = "1.38.0", features = ["full"] } serde = {version="1.0.201", features = ["derive"] } rustls-pki-types = "1.7.0" rustls-pemfile = "2.1.2" serde_json = "1.0.117" thiserror = "1.0.60" webpki = "0.22.4" webpki-roots = "0.26.3" log = "0.4.22" env_logger = "0.11.5" socket2 = "0.5.7" async-trait = "0.1.81" bincode = "1.3.3" test-log = "0.2.16" static_assertions = "1.1.0" [dev-dependencies] x509-parser = "0.16.0" local-agent-rs-1.4.3/local_agent_rs/README.md000066400000000000000000000000651474617255100205770ustar00rootroot00000000000000This is the pure rust implementation of local agent. local-agent-rs-1.4.3/local_agent_rs/src/000077500000000000000000000000001474617255100201065ustar00rootroot00000000000000local-agent-rs-1.4.3/local_agent_rs/src/agent_connection.rs000066400000000000000000000066111474617255100237750ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use crate::{ transport::Transport, AgentFeatures, Error, Request, Response, Result, StatusGet, StatusMessage, }; use std::sync::Arc; // ----------------------------------------------------------------------------- /// Represents an active connection to the LocalAgent server. /// /// One of these is needed per connection to a LocalAgent server. #[derive(Clone)] pub struct AgentConnection { transport: Arc, } impl AgentConnection { /// Creates a new AgentConnection, dont use this directly, use /// AgentConnector::connect instead. pub fn new(transport: T) -> Result where T: Transport + 'static, { Ok(Self { transport: Arc::new(transport), }) } /// Requests the local agent status. This method does not return anything. /// Eventually the local agent server will push the status, which can then /// be read via the read() method. pub async fn request_status(&self, timeout_in_seconds: u64) -> Result<()> { tokio::time::timeout( std::time::Duration::from_secs(timeout_in_seconds), async move { self.transport .send(Request { status_get: Some(StatusGet {}), features_set: None, }) .await }, ) .await? } /// Requests the local agent status. This method does not return anything. /// Eventually the local agent server will push the status, which can then /// be read via the read() method. pub async fn request_features( &self, features: AgentFeatures, timeout_in_seconds: u64, ) -> Result<()> { tokio::time::timeout( std::time::Duration::from_secs(timeout_in_seconds), async move { self.transport .send(Request { status_get: None, features_set: Some(features), }) .await }, ) .await? } /// Closes the connection. pub async fn close(&self, timeout_in_seconds: u64) -> Result<()> { tokio::time::timeout( std::time::Duration::from_secs(timeout_in_seconds), self.transport.close(), ) .await? } /// Asynchronously awaits until the local agent server pushes a response and /// returns it. pub async fn read(&self) -> Result { // Receive the response from the server. let response = self.transport.recv().await?; // Interpret the response from the server match response { // If the response contains a status message, return it. Response { status: Some(status), error: None, } => Ok(status), // If the response contains an error, return it. Response { status: _, error: Some(e), } => Err(Error::GetStatusError(e)), // If the response contains neither a status nor an error, return // an error. _ => Err(Error::NoStatusReturned), } } } local-agent-rs-1.4.3/local_agent_rs/src/agent_connector.rs000066400000000000000000000206211474617255100236250ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use crate::{ transport_playback::TransportPlayback, transport_stream::TransportStream, AgentConnection, Error, Result, }; // ----------------------------------------------------------------------------- use rustls_pki_types::CertificateDer; use std::sync::Arc; use tokio::net::TcpStream; use tokio_rustls::rustls::{version::TLS12, ClientConfig, RootCertStore}; use tokio_rustls::TlsConnector; // ----------------------------------------------------------------------------- /// The address of the local agent server, this ip is always the same const SERVER_ADDR: &str = "10.2.0.1:65432"; /// The root certificate of the local agent server, this may expire in the future /// we have a test in the gtk python application unit tests which will fail /// when this expires. const PROTON_VPN_ROOT_CA: &str = r#"-----BEGIN CERTIFICATE----- MIIFozCCA4ugAwIBAgIBATANBgkqhkiG9w0BAQ0FADBAMQswCQYDVQQGEwJDSDEV MBMGA1UEChMMUHJvdG9uVlBOIEFHMRowGAYDVQQDExFQcm90b25WUE4gUm9vdCBD QTAeFw0xNzAyMTUxNDM4MDBaFw0yNzAyMTUxNDM4MDBaMEAxCzAJBgNVBAYTAkNI MRUwEwYDVQQKEwxQcm90b25WUE4gQUcxGjAYBgNVBAMTEVByb3RvblZQTiBSb290 IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt+BsSsZg7+AuqTq7 vDbPzfygtl9f8fLJqO4amsyOXlI7pquL5IsEZhpWyJIIvYybqS4s1/T7BbvHPLVE wlrq8A5DBIXcfuXrBbKoYkmpICGc2u1KYVGOZ9A+PH9z4Tr6OXFfXRnsbZToie8t 2Xjv/dZDdUDAqeW89I/mXg3k5x08m2nfGCQDm4gCanN1r5MT7ge56z0MkY3FFGCO qRwspIEUzu1ZqGSTkG1eQiOYIrdOF5cc7n2APyvBIcfvp/W3cpTOEmEBJ7/14RnX nHo0fcx61Inx/6ZxzKkW8BMdGGQF3tF6u2M0FjVN0lLH9S0ul1TgoOS56yEJ34hr JSRTqHuar3t/xdCbKFZjyXFZFNsXVvgJu34CNLrHHTGJj9jiUfFnxWQYMo9UNUd4 a3PPG1HnbG7LAjlvj5JlJ5aqO5gshdnqb9uIQeR2CdzcCJgklwRGCyDT1pm7eoiv WV19YBd81vKulLzgPavu3kRRe83yl29It2hwQ9FMs5w6ZV/X6ciTKo3etkX9nBD9 ZzJPsGQsBUy7CzO1jK4W01+u3ItmQS+1s4xtcFxdFY8o/q1zoqBlxpe5MQIWN6Qa lryiET74gMHE/S5WrPlsq/gehxsdgc6GDUXG4dk8vn6OUMa6wb5wRO3VXGEc67IY m4mDFTYiPvLaFOxtndlUWuCruKcCAwEAAaOBpzCBpDAMBgNVHRMEBTADAQH/MB0G A1UdDgQWBBSDkIaYhLVZTwyLNTetNB2qV0gkVDBoBgNVHSMEYTBfgBSDkIaYhLVZ TwyLNTetNB2qV0gkVKFEpEIwQDELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFByb3Rv blZQTiBBRzEaMBgGA1UEAxMRUHJvdG9uVlBOIFJvb3QgQ0GCAQEwCwYDVR0PBAQD AgEGMA0GCSqGSIb3DQEBDQUAA4ICAQCYr7LpvnfZXBCxVIVc2ea1fjxQ6vkTj0zM htFs3qfeXpMRf+g1NAh4vv1UIwLsczilMt87SjpJ25pZPyS3O+/VlI9ceZMvtGXd MGfXhTDp//zRoL1cbzSHee9tQlmEm1tKFxB0wfWd/inGRjZxpJCTQh8oc7CTziHZ ufS+Jkfpc4Rasr31fl7mHhJahF1j/ka/OOWmFbiHBNjzmNWPQInJm+0ygFqij5qs 51OEvubR8yh5Mdq4TNuWhFuTxpqoJ87VKaSOx/Aefca44Etwcj4gHb7LThidw/ky zysZiWjyrbfX/31RX7QanKiMk2RDtgZaWi/lMfsl5O+6E2lJ1vo4xv9pW8225B5X eAeXHCfjV/vrrCFqeCprNF6a3Tn/LX6VNy3jbeC+167QagBOaoDA01XPOx7Odhsb Gd7cJ5VkgyycZgLnT9zrChgwjx59JQosFEG1DsaAgHfpEl/N3YPJh68N7fwN41Cj zsk39v6iZdfuet/sP7oiP5/gLmA/CIPNhdIYxaojbLjFPkftVjVPn49RqwqzJJPR N8BOyb94yhQ7KO4F3IcLT/y/dsWitY0ZH4lCnAVV/v2YjWAWS3OWyC8BFx/Jmc3W DK/yPwECUcPgHIeXiRjHnJt0Zcm23O2Q3RphpU+1SO3XixsXpOVOYP6rJIXW9bMZ A1gTTlpi7A== -----END CERTIFICATE----- "#; // ----------------------------------------------------------------------------- /// Parameters for connecting to the local agent server. /// # Fields /// /// * `domain` - The name of the local agent server to connect to. /// * `key` - The private key pks8 formatted in pem encoding. /// * `cert` - The certificate in pem encoding. /// * `timeout_in_seconds` - The timeout in seconds for the connection. /// #[derive(Debug)] pub struct ConnectParams { pub domain: String, pub key: String, pub cert: String, pub timeout_in_seconds: u64, } /// Builds the root certificate store, fom the constant PROTON_VPN_ROOT_CA. fn build_root_cert_store() -> Result { let mut cursor = std::io::Cursor::new(PROTON_VPN_ROOT_CA); let ca = rustls_pemfile::certs(&mut cursor); let mut root_cert_store = RootCertStore::empty(); for i in ca { root_cert_store.add(i?)?; } Ok(root_cert_store) } /// Parses the given pem file of certificates and returns a vector of /// valid certificates. fn parse_certificates(cert: &str) -> Result>> { let mut cursor = std::io::Cursor::new(cert); let certs = rustls_pemfile::certs(&mut cursor) .filter_map(|x| x.ok()) .collect::>>(); if certs.is_empty() { return Err(Error::NoCertificatesFound); } Ok(certs) } /// Creator of AgentConnections. /// /// You should only need one of these per application. /// see AgentConnector::connect #[derive(Clone)] pub struct AgentConnector {} impl AgentConnector { /// Connects to the LocalAgent server. /// /// Returns an AgentConnection if successful. /// /// # Arguments /// /// * `params` - The parameters to establish the agent connection. /// pub async fn connect( params: ConnectParams ) -> Result { // Build the root certificate store let root_cert_store = build_root_cert_store()?; let certs = parse_certificates(¶ms.cert)?; // Key is in pks8 format let key = rustls_pemfile::private_key(&mut std::io::Cursor::new(¶ms.key))? .ok_or(Error::NoPrivateKeyFound)?; // TLS 1.2 is forced because with 1.3 we were not getting any errors // when later we were establishing a connection with an expired certificate. // FIX-ME: see how to achieve the same with 1.3 let config = ClientConfig::builder_with_protocol_versions(&[&TLS12]) .with_root_certificates(root_cert_store) .with_client_auth_cert(certs, key)?; let connector = TlsConnector::from(Arc::new(config)); let dnsname = rustls_pki_types::ServerName::try_from(params.domain.clone())?; let timeout_duration = std::time::Duration::from_secs(params.timeout_in_seconds); let tcp_stream = tokio::time::timeout( timeout_duration, TcpStream::connect(SERVER_ADDR), ) .await??; let mut ka = socket2::TcpKeepalive::new(); ka = ka.with_time(std::time::Duration::from_secs(60)); ka = ka.with_interval(std::time::Duration::from_secs(30)); ka = ka.with_retries(3); let sock_ref = socket2::SockRef::from(&tcp_stream); sock_ref.set_tcp_keepalive(&ka)?; let connection = tokio::time::timeout( timeout_duration, connector.connect(dnsname, tcp_stream), ) .await??; let (read, write) = tokio::io::split(connection); AgentConnection::new(TransportStream::new(read, write)) } /// Reads a json of server Responses and returns an AgentConnection which /// will behave like a connection to the local agent server, but will just /// play back the responses from the json. /// /// Returns an AgentConnection if successful. /// /// # Arguments /// /// * `responses` - A string containing the responses that the Mock server. /// pub async fn playback(responses: &str) -> Result { AgentConnection::new(TransportPlayback::new(responses)?) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_proton_vpn_root_certificate_is_not_close_to_expiration() { // Ensure that the certificate still has 90 days before it expires. let ninety_days = 90_f64; // 90 days in seconds let seconds = ninety_days * 24_f64 * 60_f64 * 60_f64; // We need an in-memory reader for the certificate as we are reading // an in-memory hard coded certificate. let mut reader = std::io::Cursor::new(PROTON_VPN_ROOT_CA); // Find the certificate in the pem file for cert_der in rustls_pemfile::certs(&mut reader) { // Ensure that we have found the certificate let cert_i = cert_der.expect("Failed to get certificate"); // Parse the certificate so we can get the expiration date from it let cert = x509_parser::parse_x509_certificate(&(cert_i)) .expect("Certificate does not parse"); // Get the expiration date of the certificate in seconds let expires_in = cert .1 .tbs_certificate .validity .time_to_expiration() .expect("Failed to get expiration date") .as_seconds_f64(); // Compare this against a minimum of 90 days assert!(expires_in > seconds); } } } local-agent-rs-1.4.3/local_agent_rs/src/agent_features.rs000066400000000000000000000020451474617255100234510ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use serde::{Deserialize, Serialize}; // ----------------------------------------------------------------------------- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct AgentFeatures { #[serde(skip_serializing_if = "Option::is_none")] pub netshield_level: Option, #[serde(skip_serializing_if = "Option::is_none")] pub randomized_nat: Option, #[serde(skip_serializing_if = "Option::is_none")] pub split_tcp: Option, #[serde(skip_serializing_if = "Option::is_none")] pub port_forwarding: Option, #[serde(skip_serializing_if = "Option::is_none")] pub forwarded_port: Option, #[serde(skip_serializing_if = "Option::is_none")] pub jail: Option, #[serde(skip_serializing_if = "Option::is_none")] pub bouncing: Option, } local-agent-rs-1.4.3/local_agent_rs/src/error.rs000066400000000000000000000032211474617255100216030ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use crate::ErrorMessage; use thiserror::Error; // ----------------------------------------------------------------------------- #[derive(Error, Debug, Default)] pub enum Error { #[default] #[error("Default error")] Default, #[error("Tokio IO error: {0}")] Tokio(#[from] tokio::io::Error), #[error("Tokio Rustls error: {0}")] TokioRustls(#[from] tokio_rustls::rustls::Error), #[error("No certificates found")] NoCertificatesFound, #[error("No private key found")] NoPrivateKeyFound, #[error("No status from local agent")] NoStatusReturned, #[error("No more responses")] NoMoreResponses, #[error("Error received from local agent server")] GetStatusError(ErrorMessage), #[error("Invalid DNS name")] InvalidDnsNameError(#[from] rustls_pki_types::InvalidDnsNameError), #[error("An error from utf 8 conversion")] Utf8Error(#[from] std::str::Utf8Error), #[error("An error from json conversion")] JsonError(#[from] serde_json::Error), #[error("An error from int type conversion")] IntError(#[from] std::num::TryFromIntError), #[error("Tokio elapsed error: {0}")] TokioElapsed(#[from] tokio::time::error::Elapsed), #[error("Invalid agent connection: {0}")] InvalidAgentConnection(String), #[error("Port Forwarding: {0}")] PortForwarding(String), #[error("Bincode: {0}")] BincodeError(#[from] bincode::Error), } pub type Result = std::result::Result; local-agent-rs-1.4.3/local_agent_rs/src/lib.rs000066400000000000000000000015371474617255100212300ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- mod agent_connection; mod agent_connector; mod agent_features; mod error; mod listener; mod message; mod port_forwarding; mod reason_code; mod transport; mod transport_playback; mod transport_stream; // ----------------------------------------------------------------------------- pub use agent_connection::AgentConnection; pub use agent_connector::{AgentConnector, ConnectParams}; pub use agent_features::AgentFeatures; pub use error::{Error, Result}; pub use listener::Listener; pub use message::*; pub use port_forwarding::request_tcp_port_forwarding; pub use reason_code::*; pub use transport_playback::TransportPlayback; pub use transport_stream::TransportStream; local-agent-rs-1.4.3/local_agent_rs/src/listener.rs000066400000000000000000000110661474617255100223050ustar00rootroot00000000000000use crate::{ request_tcp_port_forwarding, AgentConnection, AgentConnector, AgentFeatures, ConnectParams, Error, Result, StatusMessage }; const DEFAULT_TIMEOUT_IN_SECONDS: u64 = 10; const PORT_FORWARDING_REFRESH_INTERVAL: u64 = 60; const MAX_PORT_FORWARDING_RETRIES: u32 = 2; fn swallow_errors(result : Result, error_message: &str) -> Option { match result { Ok(result) => Some(result), Err(error) => { log::error!("{error_message}: {error}"); None } } } #[derive(Clone)] pub struct Listener { connection: AgentConnection } impl Listener { /// Establishes the agent connection and returns a Listener object wrapping it. /// /// # Arguments /// /// * `connection_params` - The parameters to establish the agent connection. pub async fn connect(connection_params: ConnectParams) -> Result { let connection = AgentConnector::connect(connection_params).await?; Ok(Self { connection }) } /// Starts listening for local agent status updates. /// /// # Arguments /// /// * `callback` - Function that will be called with the local agent status/error as parameter. pub async fn listen ( &self, callback: C ) -> Result<()> where C: Fn(Result) -> Result<()> { let mut old_status: Option = None; loop { let new_status_future = tokio::time::timeout( std::time::Duration::from_secs(PORT_FORWARDING_REFRESH_INTERVAL), self.connection.read() ).await; match new_status_future { Ok(new_status) => { match new_status { Ok(mut new_status) => { _ = swallow_errors( self.request_port_forwarding_lease(&mut new_status).await, "Port forwarding refresh failed." ); old_status = Some(new_status.clone()); callback(Ok(new_status))?; }, Err(Error::GetStatusError(error)) => callback(Err(Error::GetStatusError(error)))?, Err(error) => { self.connection.close(DEFAULT_TIMEOUT_IN_SECONDS).await?; return Err(error) }, }; }, Err(_port_forwarding_refreh) => { // The port forwarding lease needs to be refreshed every minute. If no new status message is // received the socket read will time out and the port lease will be refreshed if required. if let Some(status) = &mut old_status { let port_changed = swallow_errors( self.request_port_forwarding_lease(status).await, "Port forwarding request failed." ); if let Some(true) = port_changed { callback(Ok(status.clone()))?; } } } }; } } async fn request_port_forwarding_lease(&self, status: &mut StatusMessage) -> Result { if let Some(AgentFeatures { port_forwarding: Some(true), forwarded_port, .. }) = &mut status.features { let port = request_tcp_port_forwarding(DEFAULT_TIMEOUT_IN_SECONDS, MAX_PORT_FORWARDING_RETRIES).await?; let some_port = Some(port); if *forwarded_port != some_port { *forwarded_port = some_port; log::info!("Forwarded port updated."); return Ok(true); }; } Ok(false) } /// Requests connection features. /// /// This method is expected to be called while listening to new agent statuses /// via the `listen()` method, and returns as soon as the request is done. /// The result is eventually sent via the `status_callback` passed to the /// `listen()` method. /// /// # Arguments /// /// * `features`: The requested features. /// * `timeout`: Amount of seconds before the request times out. pub async fn request_features( &self, features: AgentFeatures, timeout_in_seconds: u64, ) -> Result<()> { self.connection.request_features(features, timeout_in_seconds).await?; Ok(()) } } local-agent-rs-1.4.3/local_agent_rs/src/message.rs000066400000000000000000000066311474617255100221060ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use crate::agent_features::*; use serde::{Deserialize, Serialize}; /// Represents the state of the connection to the local agent client. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum State { Connected, HardJailed, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct Reason { pub code: i32, #[serde(rename = "final")] pub is_final: bool, pub description: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct ConnectionDetails { pub device_ip: Option, pub device_country: Option, pub server_ipv4: Option, pub server_ipv6: Option, } /// Represents the status message from the local agent server. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct StatusMessage { pub state: State, pub reason: Option, pub features: Option, #[serde(skip_serializing_if = "Option::is_none")] pub connection_details: Option, /* "state": "connected", "features": { "netshield-level": 2, "split-tcp": true, "bouncing": "0", "randomized-nat": false, "port-forwarding": false, "jail": false, "safe-mode": false }, "client-device-ip": "88.170.255.159", "connection-details": { "device-ip": "88.170.255.159", "device-country": "FR", "server-ipv4": "185.159.159.16" } */ } /// Represents the error message from the local agent server. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct ErrorMessage { pub code: u32, pub description: String, /* { "error":{ "code":86203, "description":"session has no fingerprint" } } */ } /// Represents the response from the local agent server. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Response { #[serde(skip_serializing_if = "Option::is_none")] pub status: Option, #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct StatusGet {} /// Represents the request to the local agent server. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Request { #[serde(skip_serializing_if = "Option::is_none")] pub features_set: Option, #[serde(skip_serializing_if = "Option::is_none")] pub status_get: Option, } impl Request { /// Creates a new Request with the given features set. pub fn new_features_set(features_set: AgentFeatures) -> Self { Self { features_set: Some(features_set), status_get: None, } } /// Creates a new Request with the status get. pub fn new_status_get() -> Self { Self { features_set: None, status_get: Some(StatusGet {}), } } } local-agent-rs-1.4.3/local_agent_rs/src/port_forwarding.rs000066400000000000000000000333521474617255100236700ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- /// This module provides the ability to request port forwarding from the proton /// vpn NAT-PMP gateway. /// /// The NAT-PMP protocol is used to request port forwarding from a NAT gateway. /// This is useful when a device is behind a NAT gateway and needs to receive /// incoming connections. /// /// This will only work if the proton vpn client is running and connected to /// a proton vpn server. /// /// Its not possible to choose the external port, the proton vpn gateway will /// choose the external port, it's also not possible to choose the internal port, /// the internal port is the same as the external port. /// /// This module supports both udp and tcp port forwarding. /// /// Details of nat-pmp protocol can be found here /// http://miniupnp.free.fr/nat-pmp.html /// use crate::error::*; use async_trait::async_trait; use serde::{Deserialize, Serialize}; // We want to display logs when running tests, but log module doesnt work in // tests. So we use println! for logs in tests. mod log { #[cfg(not(test))] pub use ::log::info; // Use log crate when building application #[cfg(test)] pub use std::println as info; // Workaround to use prinltn! for logs. } const NAT_PMP_ADDRESS: &str = "10.2.0.1:5351"; const PROTOCOL_VERSION_NATPMP: u8 = 0; const DEFAULT_PORT_LIFETIME: u32 = 60; /// The different types of NAT-PMP request operations that can be made. #[repr(u8)] #[derive(Clone)] pub enum NatPmpRequestOp { #[allow(dead_code)] ExternalAddress = 0, #[allow(dead_code)] Udp = 1, Tcp = 2, } /// The different types of NAT-PMP response operations that can be received. #[repr(u8)] pub enum NatPmpReplyOp { Udp = 129, Tcp = 130, } /// The different types of NAT-PMP response codes that can be received, these /// are either success or an error code. #[repr(u16)] #[derive(Debug)] pub enum NatPmpResponseCode { Success, // 0 #[allow(dead_code)] UnsupportedVersion, // 1 #[allow(dead_code)] NotAuthorized, // 2 #[allow(dead_code)] NetworkFailure, // 3 #[allow(dead_code)] OutOfResources, // 4 #[allow(dead_code)] UnsupportedOpcode, // 5 } /// The request struct that is sent to the NAT-PMP gateway. #[derive(Serialize, Deserialize, Debug)] struct Request { version: u8, // The version of the protocol. 0 for NAT-PMP, 1 for PCP. operation: u8, // The operation to perform, see NatPmpRequestOp. reserved: u16, // Reserved field, must be 0. internal_port: u16, // The internal (client) port. external_port: u16, // The external (server ) port. lifetime_seconds: u32, // The lifetime of the port forwarding in seconds. } impl Request { pub fn new(operation: NatPmpRequestOp) -> Self { Request { version: PROTOCOL_VERSION_NATPMP, operation: operation as u8, reserved: 0, internal_port: 0, external_port: 0, lifetime_seconds: DEFAULT_PORT_LIFETIME, } } } #[derive(Serialize, Deserialize, Debug)] struct Response { version: u8, // The version of the protocol. 0 for NAT-PMP, 1 for PCP. operation: u8, // The operation that was performed, see NatPmpReplyOp. response_code: u16, // The status of the requested operation, see NatPmpResponseCode. gateway_epoch_seconds: u32, // Seconds since port mapping table was initialized internal_port: u16, // The internal (client) port. external_port: u16, // The external (server) port. lifetime_seconds: u32, // The lifetime of this new port forwarding in seconds. } /// This is an abstraction for the network transport used by the port mapping /// module. /// Creating an abstraction allows us to mock the transport layer and test /// the module. #[async_trait] pub trait PortForwardingTransport { async fn send(&self, request: &[u8]) -> Result<()>; async fn recv(&self) -> Result<[u8; 16]>; } /// This is the udp based implementation of the PortForwardingTransport, /// which is what we use in production. struct NetworkTransport { socket: tokio::net::UdpSocket, } impl NetworkTransport { pub async fn connect_to_nmap_server() -> Result { let socket = tokio::net::UdpSocket::bind(std::net::SocketAddr::V4( std::net::SocketAddrV4::new(std::net::Ipv4Addr::UNSPECIFIED, 0), )) .await?; socket.connect(NAT_PMP_ADDRESS).await?; Ok(Self { socket }) } } /// Implement the PortForwardingTransport trait for NetworkTransport. /// This allows the request_port_forwarding function to use the NetworkTransport /// to send and receive messages over a udp socket #[async_trait] impl PortForwardingTransport for NetworkTransport { async fn send(&self, request: &[u8]) -> Result<()> { self.socket.send(request).await?; Ok(()) } async fn recv(&self) -> Result<[u8; 16]> { const BYTE_SIZE: usize = std::mem::size_of::(); static_assertions::const_assert_eq!(BYTE_SIZE, 16); let mut response_bytes = [0_u8; 16]; let bytes_read = self.socket.recv(&mut response_bytes).await?; if bytes_read != BYTE_SIZE { return Err(Error::PortForwarding(format!( "Protocol error incorrect number of bytes returned in \ nat-pmp response {}", bytes_read ))); } Ok(response_bytes) } } /// Request a UDP port forwarding from the NAT-PMP gateway. /// Returns the internal port that was mapped. /// The external (server) port is chosen by the gateway and is the same as /// the internal (client) port. /// /// If the port forwarding fails, an error is returned. /// async fn request_port_forwarding( transport: &impl PortForwardingTransport, operation: NatPmpRequestOp, timeout_in_seconds: u64, max_retries: u32, ) -> Result { let mut timeout_duration = std::time::Duration::from_secs(timeout_in_seconds); use bincode::Options as _; let serializer = bincode::DefaultOptions::new() .with_fixint_encoding() .allow_trailing_bytes() .with_big_endian(); let request = Request::new(operation.clone()); let request_u8: Vec = serializer.serialize(&request)?; for attempt in 0..max_retries + 1 { log::info!("Sending {:?} attempt {}", &request, attempt); // We use tokio::time::timeout to set a timeout on the send // because send can block in cases where routing to the destination // fails. // // Send could also block if the output buffer is full, but we are // sending a small message so this is unlikely. // // We want a timeout on send to actually fail, we don't want to rety // the send. tokio::time::timeout( timeout_duration, transport.send(&request_u8.clone()), ) .await??; // We're putting a timeout on recv because the sent message could be // dropped at any point in the network, so we want to timeout and retry // the send and recv. // // The only guarantee that we have that the send was successful is that // we get a response. match tokio::time::timeout(timeout_duration, transport.recv()).await { Ok(Ok(response_u8)) => { let response: Response = serializer.deserialize(&response_u8)?; log::info!("Receiving {:?}", response); if response.response_code != NatPmpResponseCode::Success as u16 { return Err(Error::PortForwarding(format!( "Failed to request port forwarding, response code: {}", response.response_code ))); } type Req = NatPmpRequestOp; type Rep = NatPmpReplyOp; match operation { Req::Tcp if response.operation == Rep::Tcp as u8 => (), Req::Udp if response.operation == Rep::Udp as u8 => (), _ => { return Err(Error::PortForwarding(format!( "Failed to request port forwarding, unexpected response code: {}", response.response_code ))); } } return Ok(response.internal_port); } Err(_elapsed) => { timeout_duration *= 2; log::info!( "Hit timeout, retrying with longer timeout {:?}", timeout_duration ); } Ok(Err(e)) => return Err(e), } } Err(Error::PortForwarding( "Exhausted maximum retries".to_string(), )) } /// Request a UDP port forwarding from the NAT-PMP gateway. /// Returns the internal port that was mapped. /// The external (server) port is chosen by the gateway and is the same as /// the internal (client) port. /// /// If the port forwarding fails, an error is returned. /// pub async fn request_tcp_port_forwarding( timeout_in_seconds: u64, max_retries: u32, ) -> Result { // Create the network transport object let transport = tokio::time::timeout( std::time::Duration::from_secs(timeout_in_seconds), NetworkTransport::connect_to_nmap_server(), ) .await??; // Request the port forwarding request_port_forwarding( &transport, NatPmpRequestOp::Tcp, timeout_in_seconds, max_retries, ) .await } #[cfg(test)] mod tests { use super::*; struct MockNetworkTransport { response: [u8; 16], } #[async_trait] impl PortForwardingTransport for MockNetworkTransport { async fn send(&self, request: &[u8]) -> Result<()> { assert_eq!(request.len(), 12); // Version assert_eq!(request[0], PROTOCOL_VERSION_NATPMP); // Operation assert_eq!(request[1], NatPmpRequestOp::Tcp as u8); // Reserved assert_eq!(u16::from_be_bytes([request[2], request[3]]), 0_u16); // Internal port assert_eq!(u16::from_be_bytes([request[4], request[5]]), 0_u16); // External port assert_eq!(u16::from_be_bytes([request[6], request[7]]), 0_u16); // // Lifetime assert_eq!( u32::from_be_bytes([ request[8], request[9], request[10], request[11] ]), DEFAULT_PORT_LIFETIME ); Ok(()) } async fn recv(&self) -> Result<[u8; 16]> { Ok(self.response.clone()) } } // Launches async runtime for test #[tokio::test] async fn test_request_port_forwarding() -> Result<()> { use bincode::Options as _; let serializer = bincode::DefaultOptions::new() .with_fixint_encoding() .allow_trailing_bytes() .with_big_endian(); let response = serializer.serialize(&Response { version: PROTOCOL_VERSION_NATPMP, operation: NatPmpReplyOp::Tcp as u8, response_code: NatPmpResponseCode::Success as u16, gateway_epoch_seconds: 1234_u32, internal_port: 123_u16, external_port: 456_u16, lifetime_seconds: 5678_u32, })?; use std::convert::TryInto as _; let mock_transport = MockNetworkTransport { response: response.try_into().expect("Unable to convert response"), // nosemgrep: panic-in-function-returning-result }; assert_eq!( request_port_forwarding( &mock_transport, NatPmpRequestOp::Tcp, 2, 3, ) .await?, 123_u16 ); Ok(()) } // Launches async runtime for test #[tokio::test] async fn test_failed_port_forwarding() -> Result<()> { use bincode::Options as _; let serializer = bincode::DefaultOptions::new() .with_fixint_encoding() .allow_trailing_bytes() .with_big_endian(); let response = serializer.serialize(&Response { version: PROTOCOL_VERSION_NATPMP, operation: NatPmpReplyOp::Tcp as u8, response_code: NatPmpResponseCode::OutOfResources as u16, gateway_epoch_seconds: 0_u32, internal_port: 0_u16, external_port: 0_u16, lifetime_seconds: 0_u32, })?; use std::convert::TryInto as _; let mock_transport = MockNetworkTransport { response: response.try_into().expect("Unable to convert response"), // nosemgrep: panic-in-function-returning-result }; match request_port_forwarding( &mock_transport, NatPmpRequestOp::Tcp, 2, 3, ) .await { Err(Error::PortForwarding(e)) => { assert_eq!( e, "Failed to request port forwarding, response code: 4" ); } _ => assert!(false), } Ok(()) } // Uncomment this test to manually test port forwarding in a vpn environment // // Launches async runtime for test // #[tokio::test] // async fn test_real_port_forwarding() -> Result<()> { // request_port_forwarding( // &NetworkTransport::connect_to_nmap_server().await?, // NatPmpRequestOp::Tcp, // 2, // 3, // ) // .await?; // Ok(()) // } } local-agent-rs-1.4.3/local_agent_rs/src/reason_code.rs000066400000000000000000000025161474617255100227410ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- /// Represents the reason for a status message. /// The reason code is used to indicate why a connection is jailed. pub const REASON_CODE_UNKNOWN: i32 = 0; pub const REASON_CODE_GUEST_SESSION: i32 = 86100; pub const REASON_CODE_RESTRICTED_SERVER: i32 = 86104; pub const REASON_CODE_BAD_CERT_SIGNATURE: i32 = 86105; pub const REASON_CODE_CERT_NOT_PROVIDED: i32 = 86106; pub const REASON_CODE_CERTIFICATE_EXPIRED: i32 = 86101; pub const REASON_CODE_CERTIFICATE_REVOKED: i32 = 86102; pub const REASON_CODE_MAX_SESSIONS_UNKNOWN: i32 = 86110; pub const REASON_CODE_MAX_SESSIONS_FREE: i32 = 86111; pub const REASON_CODE_MAX_SESSIONS_BASIC: i32 = 86112; pub const REASON_CODE_MAX_SESSIONS_PLUS: i32 = 86113; pub const REASON_CODE_MAX_SESSIONS_VISIONARY: i32 = 86114; pub const REASON_CODE_MAX_SESSIONS_PRO: i32 = 86115; pub const REASON_CODE_KEY_USED_MULTIPLE_TIMES: i32 = 86103; pub const REASON_CODE_SERVER_ERROR: i32 = 86150; pub const REASON_CODE_POLICY_VIOLATION_LOW_PLAN: i32 = 86151; pub const REASON_CODE_POLICY_VIOLATION_DELINQUENT: i32 = 86152; pub const REASON_CODE_USER_TORRENT_NOT_ALLOWED: i32 = 86153; pub const REASON_CODE_USER_BAD_BEHAVIOR: i32 = 86154; local-agent-rs-1.4.3/local_agent_rs/src/transport.rs000066400000000000000000000010561474617255100225120ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use crate::{Request, Response, Result}; use async_trait::async_trait; /// Represents a transport layer, such as a TCP stream or a Unix domain socket, /// or a file. #[async_trait] pub trait Transport: Send + Sync { async fn send(&self, request: Request) -> Result<()>; async fn recv(&self) -> Result; async fn close(&self) -> Result<()>; } local-agent-rs-1.4.3/local_agent_rs/src/transport_playback.rs000066400000000000000000000037351474617255100243660ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use crate::{Error, transport::Transport, Request, Response, Result}; use async_trait::async_trait; use tokio::sync::Mutex; // ----------------------------------------------------------------------------- /// The responses list contains seconds to wait + the response. /// (seconds, Response) type Responses = Vec<(u64, Response)>; /// Implements a dummy transport layer for testing purposes. /// The requests to the server are ignored and responses are hard coded. pub struct TransportPlayback { responses: Mutex, } impl TransportPlayback { pub fn new(responses_str: &str) -> Result { // The responses list contains seconds to wait + the response per entry. // // responses = [ (seconds, response), ... ] // let mut responses: Responses = serde_json::from_str(responses_str)?; responses.reverse(); Ok(Self { responses: Mutex::new(responses), }) } } #[async_trait] impl Transport for TransportPlayback { /// Implements send method, but this implementation does nothing, /// it just drops the request. async fn send(&self, _request: Request) -> Result<()> { Ok(()) } /// Implements recv method, this just returns the next response /// in the responses list, which is read from a json file. async fn recv(&self) -> Result { // Get the next response let (seconds, response) = self .responses .lock() .await .pop().ok_or(Error::NoMoreResponses)?; // First we wait a bit before we return the response std::thread::sleep(std::time::Duration::from_secs(seconds)); // Return the response Ok(response) } async fn close(&self) -> Result<()> { Ok(()) } } local-agent-rs-1.4.3/local_agent_rs/src/transport_stream.rs000066400000000000000000000067071474617255100240750ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use crate::{transport::Transport, Error, Request, Response, Result}; use async_trait::async_trait; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::Mutex; // ----------------------------------------------------------------------------- /// Implements a transport layer using a tokio AsyncReadExt and AsyncWriteExt /// trait, this can be a TLS stream or a TCP stream or anything that implements /// these traits. pub struct TransportStream where Read: AsyncReadExt + std::marker::Unpin + Send, Write: AsyncWriteExt + std::marker::Unpin + Send, { read: Mutex>, write: Mutex>, } impl TransportStream where Read: AsyncReadExt + std::marker::Unpin + Send, Write: AsyncWriteExt + std::marker::Unpin + Send, { pub fn new(read: Read, write: Write) -> Self { Self { read: Mutex::new(Some(read)), write: Mutex::new(Some(write)), } } } #[async_trait] impl Transport for TransportStream where Read: AsyncReadExt + std::marker::Unpin + Send, Write: AsyncWriteExt + std::marker::Unpin + Send, { async fn send(&self, request: Request) -> Result<()> { if let Some(write) = self.write.lock().await.as_mut() { // Serialize the request to a JSON string. let payload = serde_json::to_vec(&request)?; // Ensure the payload is not too large. assert!(payload.len() <= u32::MAX as usize); // Convert the payload length to a big-endian byte array. let payload_length: [u8; 4] = (payload.len() as u32).to_be_bytes(); // Send the payload length to the server. write.write_all(&payload_length).await?; // Send the payload to the server. log::info!("Sending: {:?}", &request); write.write_all(&payload).await?; Ok(()) } else { Err(Error::InvalidAgentConnection( "Disconnected stream".to_string(), )) } } async fn recv(&self) -> Result { if let Some(read) = self.read.lock().await.as_mut() { log::info!("Receiving..."); // Read the payload length from the server. let response_length: usize = read.read_u32().await?.try_into()?; // Allocate a buffer of that length. let mut buf = vec![0u8; response_length]; // Read the payload read.read_exact(&mut buf).await?; let response: Response = serde_json::from_slice(&buf)?; log::info!(" {:?}", &response); // Deserialize the response from the JSON string. Ok(response) } else { Err(Error::InvalidAgentConnection( "Disconnected stream".to_string(), )) } } async fn close(&self) -> Result<()> { // Only call shutdown if the stream has not already been closed down if let Some(mut write) = self.write.lock().await.take() { // First explicitly signal the stream shutdown by sending // null character. write.shutdown().await?; } // Drop the read stream self.read.lock().await.take(); Ok(()) } } local-agent-rs-1.4.3/local_agent_rs/tests/000077500000000000000000000000001474617255100204615ustar00rootroot00000000000000local-agent-rs-1.4.3/local_agent_rs/tests/agent_connection/000077500000000000000000000000001474617255100237765ustar00rootroot00000000000000local-agent-rs-1.4.3/local_agent_rs/tests/agent_connection/main.rs000066400000000000000000000023571474617255100252770ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- mod server; use local_agent_rs::AgentConnection; use local_agent_rs::State; use local_agent_rs::TransportStream; use server::Server; use tokio::net::TcpStream; #[tokio::test] async fn test_request_status() { // The server address let server_addr = String::from("127.0.0.1:8080"); // Create a new server let _server = Server::new(&server_addr) .await .expect("Server couldn't be created"); // Create a new TCP stream let tcp_stream = TcpStream::connect(server_addr) .await .expect("TCP stream couldn't be open"); let (read, write) = tokio::io::split(tcp_stream); // Create a new AgentConnection // and send a request to get the status let connection = AgentConnection::new(TransportStream::new(read, write)) .expect("AgentConnection couldn't be created"); connection .request_status(1) .await .expect("get-status failed"); let response = connection.read().await.expect("read failed"); assert!(std::matches!(response.state, State::Connected)); } local-agent-rs-1.4.3/local_agent_rs/tests/agent_connection/server.rs000066400000000000000000000047301474617255100256560ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use local_agent_rs::{Request, Response, Result, State, StatusMessage}; use tokio::io::AsyncReadExt; use tokio::io::AsyncWriteExt; use tokio::net::TcpListener; use tokio::sync::mpsc; use tokio::sync::mpsc::Sender; pub struct Server { _server_task: tokio::task::JoinHandle>, } impl Server { /// Create a new Server instance. /// # Arguments /// * `address` - The address to bind the server to. /// # Returns /// A new Server instance. /// # Errors /// Returns an error if the server couldn't be created. pub async fn new(address: &str) -> Result { let server_addr = address.to_owned(); let (tx, mut rx) = mpsc::channel::(100); let task = tokio::spawn(async move { Server::run(server_addr, tx).await }); rx.recv().await; Ok(Server { _server_task: task }) } async fn run(addr: String, sender: Sender) -> Result<()> { let listener = TcpListener::bind(&addr).await?; let _ = sender.send(0).await; log::info!("Listening on: {}", addr); let (mut socket, _) = listener.accept().await?; // In a loop, read data from the socket and write the data back. log::info!("Waiting for data..."); let request_length: usize = socket.read_u32().await?.try_into()?; let mut buf = vec![0u8; request_length]; socket.read_exact(&mut buf).await?; let request_str = std::str::from_utf8(&buf)?; log::info!("received: {}", request_str); let _request = serde_json::from_str::(request_str) .expect("unable to deserialize Request"); let response = Response { status: Some(StatusMessage { state: State::Connected, reason: None, features: None, connection_details: None, }), error: None, }; let response_str = serde_json::to_string(&response) .expect("unable to serialize Response"); log::info!("sending: {}", &response_str); let payload = response_str.into_bytes(); let payload_length: [u8; 4] = (payload.len() as u32).to_be_bytes(); socket.write_all(&payload_length).await?; socket.write_all(&payload).await?; Ok(()) } } local-agent-rs-1.4.3/python-proton-vpn-local-agent/000077500000000000000000000000001474617255100221705ustar00rootroot00000000000000local-agent-rs-1.4.3/python-proton-vpn-local-agent/Cargo.lock000066400000000000000000001064041474617255100241020ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[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", ] [[package]] name = "anstyle-wincon" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "async-trait" version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", "syn 2.0.72", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "aws-lc-rs" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae74d9bd0a7530e8afd1770739ad34b36838829d6ad61818f9230f683f5ad77" dependencies = [ "aws-lc-sys", "mirai-annotations", "paste", "zeroize", ] [[package]] name = "aws-lc-sys" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0e249228c6ad2d240c2dc94b714d711629d52bad946075d8e9b2f5391f0703" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", "libc", "paste", ] [[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", "object", "rustc-demangle", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ "serde", ] [[package]] name = "bindgen" version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools", "lazy_static", "lazycell", "log", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn 2.0.72", "which", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bytes" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" 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-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "cmake" version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ "cc", ] [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_filter" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", "env_filter", "humantime", "log", ] [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn 2.0.72", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" 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 = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ "windows-sys", ] [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indoc" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[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 = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets", ] [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-agent-rs" version = "0.10.0" dependencies = [ "async-trait", "bincode", "env_logger", "log", "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "socket2", "static_assertions", "test-log", "thiserror", "tokio", "tokio-rustls", "webpki", "webpki-roots", ] [[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 = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ "hermit-abi", "libc", "wasi", "windows-sys", ] [[package]] name = "mirai-annotations" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" [[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 = "object" version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[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", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "prettyplease" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", "syn 2.0.72", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "parking_lot", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-asyncio-0-21" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fde289486f7d5cee0ac7c20b2637a0657654681079cc5eedc90d9a2a79af1e5" dependencies = [ "futures", "once_cell", "pin-project-lite", "pyo3", "pyo3-asyncio-macros-0-21", "tokio", ] [[package]] name = "pyo3-asyncio-macros-0-21" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e5ffc4e987e866bf54b781235a6c3b91e7e67df14f73ce716625ee78728554a" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "pyo3-build-config" version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn 2.0.72", ] [[package]] name = "pyo3-macros-backend" version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn 2.0.72", ] [[package]] name = "python-proton-vpn-local-agent" version = "1.4.3" dependencies = [ "env_logger", "local-agent-rs", "log", "pyo3", "pyo3-asyncio-0-21", "thiserror", "tokio", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "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.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[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", ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rustls" version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "aws-lc-rs", "log", "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64", "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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", "syn 2.0.72", ] [[package]] name = "serde_json" version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[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 = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[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.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.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "test-log" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" dependencies = [ "env_logger", "test-log-macros", "tracing-subscriber", ] [[package]] name = "test-log-macros" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", "syn 2.0.72", ] [[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.72", ] [[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 = "tokio" version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys", ] [[package]] name = "tokio-macros" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", "syn 2.0.72", ] [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", "tokio", ] [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", ] [[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-subscriber" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unindent" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "webpki" version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ "ring", "untrusted", ] [[package]] name = "webpki-roots" version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" 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", "home", "once_cell", "rustix", ] [[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-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" 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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[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.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[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.72", ] local-agent-rs-1.4.3/python-proton-vpn-local-agent/Cargo.toml000066400000000000000000000011471474617255100241230ustar00rootroot00000000000000[package] name = "python-proton-vpn-local-agent" version = "1.4.3" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["cdylib"] [profile.release] # Strip debug symbols in release builds strip = true [dependencies] local-agent-rs = { path = "../local_agent_rs" } pyo3 = {version="0.21.2", features = ["extension-module", "abi3-py38"]} pyo3-asyncio-0-21 = {version="0.21.0", features = ["attributes", "tokio-runtime"]} tokio = { version = "1.38.0", features = ["full"] } thiserror = "1.0.60" log = "0.4.22" env_logger = "0.11.5" local-agent-rs-1.4.3/python-proton-vpn-local-agent/debian/000077500000000000000000000000001474617255100234125ustar00rootroot00000000000000local-agent-rs-1.4.3/python-proton-vpn-local-agent/debian/control000066400000000000000000000005561474617255100250230ustar00rootroot00000000000000Source: python3-proton-vpn-local-agent Maintainer: Proton AG Package: python3-proton-vpn-local-agent Architecture: {{architecture}} Depends: python3 (>= 3.9), libc6 (>= 2.31), libgcc-s1 (>= 4.2) Section: utils Priority: optional Description: Local Agent Python Module A python module which implements a client for the Proton Local Agent API.local-agent-rs-1.4.3/python-proton-vpn-local-agent/debian/copyright000066400000000000000000000005241474617255100253460ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Source: https://github.com/ProtonVPN/ Upstream-Name: python3-proton-vpn-local-agent Files: * Copyright: 2023 Proton AG License: GPL-3 The full text of the GPL version 3 is distributed in /usr/share/common-licenses/GPL-3 on Debian systems.local-agent-rs-1.4.3/python-proton-vpn-local-agent/debian/rules000077500000000000000000000005431474617255100244740ustar00rootroot00000000000000#!/usr/bin/make -f clean: @# Do nothing build: @# Do nothing binary: mkdir -p debian/python3-proton-vpn-local-agent/usr/lib/python3/dist-packages/proton/vpn cp {{rust_triplet}}/release/libpython_proton_vpn_local_agent.so debian/python3-proton-vpn-local-agent/usr/lib/python3/dist-packages/proton/vpn/local_agent.abi3.so dh_gencontrol dh_builddeb local-agent-rs-1.4.3/python-proton-vpn-local-agent/rpmbuild/000077500000000000000000000000001474617255100240065ustar00rootroot00000000000000local-agent-rs-1.4.3/python-proton-vpn-local-agent/rpmbuild/SPECS/000077500000000000000000000000001474617255100246635ustar00rootroot00000000000000local-agent-rs-1.4.3/python-proton-vpn-local-agent/rpmbuild/SPECS/package.spec.template000066400000000000000000000005501474617255100307440ustar00rootroot00000000000000Name: {PACKAGE_NAME} Release: 1%{{?dist}} Summary: A client for interacting with local agent License: GPLv3 Version: {VERSION} URL: https://github.com/ProtonVPN/python-proton-vpn-local-agent Requires: python3 >= {CPYTHON_VERSION} %description A client for interacting with local agent %files /{install_path} %changeloglocal-agent-rs-1.4.3/python-proton-vpn-local-agent/scripts/000077500000000000000000000000001474617255100236575ustar00rootroot00000000000000local-agent-rs-1.4.3/python-proton-vpn-local-agent/scripts/build_rpm.py000077500000000000000000000044121474617255100262120ustar00rootroot00000000000000#!/bin/env python3 # ------------------------------------------------------------------------------ # Copyright (c) 2024 Proton AG # ------------------------------------------------------------------------------ ''' This searches for a shared library for local agent and packages it inside an .rpm file. ''' # ------------------------------------------------------------------------------ import argparse import os import shutil import subprocess # nosemgrep # ------------------------------------------------------------------------------ import devtools.versions from package_info import (get_versions, get_lib_path, MODULE_PATH, PACKAGE_NAME, PROTON_VPN_NAMESPACE, PYTHON_EXTENSION_NAME, CPYTHON_VERSION, HOME, VERSION, TIME) parser = argparse.ArgumentParser() parser.add_argument("fedora_version") parser.add_argument("rpm_arch") parser.add_argument("rust_triplet") args = parser.parse_args() FEDORA_VERSION = f"fc{args.fedora_version}" RPM_ARCH = args.rpm_arch RUST_TRIPLET = args.rust_triplet install_path = os.path.join( 'usr', 'lib64', f"python{CPYTHON_VERSION}", 'site-packages', *(PROTON_VPN_NAMESPACE.split("-")) ) SPEC_TEMPLATE = MODULE_PATH / "rpmbuild" / "SPECS" / "package.spec.template" BUILDROOT =\ os.path.join(HOME, "rpmbuild", "BUILDROOT", f"{PACKAGE_NAME}-{VERSION}-1.{FEDORA_VERSION}.{RPM_ARCH}") module_path = os.path.join(BUILDROOT, install_path) os.makedirs(f"target/rpmbuild/{PACKAGE_NAME}/SPECS", exist_ok=True) os.makedirs(module_path, exist_ok=True) devtools.versions.build_rpm( f"target/rpmbuild/{PACKAGE_NAME}/SPECS/package.spec", get_versions(), SPEC_TEMPLATE, additional_variables={ "PACKAGE_NAME": PACKAGE_NAME, "VERSION": VERSION, "CPYTHON_VERSION": CPYTHON_VERSION, "install_path": install_path, } ) lib_path = get_lib_path(RUST_TRIPLET) shutil.copyfile(lib_path, os.path.join(module_path, PYTHON_EXTENSION_NAME)) subprocess.check_output(["rpmbuild", "--quiet", "-bb", "--buildroot", BUILDROOT, "--target", RPM_ARCH, "package.spec"], cwd=f"target/rpmbuild/{PACKAGE_NAME}/SPECS/") print(TIME) local-agent-rs-1.4.3/python-proton-vpn-local-agent/scripts/build_wheel.py000077500000000000000000000045301474617255100265210ustar00rootroot00000000000000#!/bin/env python3 # ------------------------------------------------------------------------------ # Copyright (c) 2023 Proton AG # ------------------------------------------------------------------------------ ''' This searches for a shared library for local agent and packages it inside a wheel file. ''' # ------------------------------------------------------------------------------ from base64 import urlsafe_b64encode import hashlib import os import zipfile # ------------------------------------------------------------------------------ from package_info import MODULE_NAME, get_lib_path, CPYTHON_MIN, CPYTHON_MAX, OS, VERSION, BUILD_DIR, PYTHON_EXTENSION_PATH PYTHON_PACKAGE_NAME = MODULE_NAME.removeprefix("python-").replace("-", "_") # Since the wheel is only used for dev purposes, it's only built for x86_64. ARCH = "x86_64" LIB_PATH = get_lib_path("x86_64-unknown-linux-gnu") def compute_digest(data: bytes): """Return (hash, length) for path using hashlib.sha256()""" if isinstance(data, str): data = data.encode('utf-8') h = hashlib.sha256() h.update(data) digest = 'sha256=' + urlsafe_b64encode( h.digest() ).decode('latin1').rstrip('=') length = len(data) return [digest, str(length)] wheel_tag = f"{CPYTHON_MIN}-"\ f"{CPYTHON_MAX}-"\ f"{OS}_"\ f"{ARCH}" wheel_filepath = os.path.join( BUILD_DIR, f"{PYTHON_PACKAGE_NAME}-{VERSION}-{wheel_tag}.whl") record = [] def write_file(wheel, file_path, data): wheel.writestr(file_path, data) record.append(",".join([file_path] + compute_digest(data))) with zipfile.ZipFile(wheel_filepath, 'w') as wheel: # The module directory # The .so file with open(LIB_PATH, 'rb') as f: write_file(wheel, PYTHON_EXTENSION_PATH, f.read()) # Metadata directory metadata = f'{PYTHON_PACKAGE_NAME}-{VERSION}.dist-info' # METADATA write_file( wheel, f"{metadata}/METADATA", 'Metadata-Version: 2.3\n' f'Name: {PYTHON_PACKAGE_NAME}\n' f'Version: {VERSION}\n') # WHEEL write_file( wheel, f"{metadata}/WHEEL", 'Wheel-Version: 1.0\n' 'Generator: None\n' 'Root-Is-Purelib: false\n' f'Tag: {wheel_tag}\n') # RECORD record.append(f"{metadata}/RECORD,,") wheel.writestr(f"{metadata}/RECORD", '\n'.join(record) + '\n') local-agent-rs-1.4.3/python-proton-vpn-local-agent/scripts/create_changelogs.py000077500000000000000000000011221474617255100276650ustar00rootroot00000000000000#!/usr/bin/env python3 ''' This program generates a deb changelog file for this project. It reads versions.yml. ''' import os import devtools.versions as versions from package_info import PACKAGE_NAME, get_versions, MODULE_PATH # The root of this repo DEB = os.path.join(MODULE_PATH, "debian", "changelog") # Path of debian # changelog. def build(): ''' This is what generates the deb changelog. ''' # Make our files versions.build_deb(DEB, get_versions(), PACKAGE_NAME) if __name__ == "__main__": build() local-agent-rs-1.4.3/python-proton-vpn-local-agent/scripts/devtools/000077500000000000000000000000001474617255100255165ustar00rootroot00000000000000local-agent-rs-1.4.3/python-proton-vpn-local-agent/scripts/package_info.py000066400000000000000000000067501474617255100266470ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # Copyright (c) 2023 Proton AG # ------------------------------------------------------------------------------ ''' This file contains the constants and function that are common across the build_* scripts. ''' # ------------------------------------------------------------------------------ import datetime import os import re import sys import pathlib # ------------------------------------------------------------------------------ PROJECT_DIR = (pathlib.Path(__file__).parent / ".." / "..").resolve() NAME = 'local-agent' PROTON_VPN_NAMESPACE = 'proton-vpn' # ------------------------------------------------------------------------------ PROTON_PREFIX = f'python-{PROTON_VPN_NAMESPACE}-' MODULE_NAME = f'{PROTON_PREFIX}{NAME}' MODULE_PATH = pathlib.Path(PROJECT_DIR) / MODULE_NAME OS = "linux" # The operating system we're building for CPYTHON_MAJOR = sys.version_info.major CPYTHON_MINOR = sys.version_info.minor PYTHON_MODULE_NAME = MODULE_NAME.removeprefix(PROTON_PREFIX).replace("-", "_") PACKAGE_NAME = MODULE_NAME.replace("python", f"python{CPYTHON_MAJOR}") BUILD_DIR = MODULE_PATH / "target" CARGO = MODULE_PATH / "Cargo.toml" CPYTHON_MIN = f"cp{CPYTHON_MAJOR}{CPYTHON_MINOR}" # Minimum supported version of c python CPYTHON_MAX = "abi3" # Maximum supported version is c python 3.x CPYTHON_VERSION = f"{CPYTHON_MAJOR}.{CPYTHON_MINOR}" # cpython version PYTHON_EXTENSION_NAME = f'{PYTHON_MODULE_NAME}.{CPYTHON_MAX}.so' PYTHON_EXTENSION_PATH = os.path.sep.join( PROTON_VPN_NAMESPACE.split("-") + [PYTHON_EXTENSION_NAME] ) # The build folder for this project. # The build process should not write any files outside of this folder. HOME = os.path.expanduser('~') VERSIONS = MODULE_PATH / "versions.yml" def get_lib_path(triplet: str): """ Get the path to the shared library for the given triplet. """ return os.path.join( PROJECT_DIR, MODULE_NAME, "target", triplet, 'release', f'lib{MODULE_NAME.replace("-", "_")}.so' ) def get_versions(): """" Get the versions of this project """ import yaml # Load versions.yml with open(VERSIONS, encoding="utf-8") as versions_file: return list(yaml.safe_load_all(versions_file)) def get_changelog_time(): """" Get the latest changelog time from versions.yml """ changelog_time = None CHANGELOG_RE = re.compile(r'^time:\s*(.*)') with open(VERSIONS, encoding="utf-8") as versions: for line in versions.readlines(): version_match = CHANGELOG_RE.match(line) if version_match: changelog_time = version_match.groups()[0] break if not changelog_time: raise ValueError("Cant find the changelog time in versions.yml file") dt = datetime.datetime.strptime(changelog_time, '%Y/%m/%d %H:%M') return dt.strftime(r"%Y-%m-%d %H:%M:%S") def get_version_from_cargo(): """ Get the version from Cargo.toml """ version = None VERSION_RE = re.compile(r'^version = "(.*)"$') with open(CARGO, encoding="utf-8") as cargo: for line in cargo.readlines(): version_match = VERSION_RE.match(line) if version_match: version = version_match.groups()[0] if not version: raise ValueError("Cant find version in Cargo.toml file") return version VERSION = get_version_from_cargo() TIME = get_changelog_time()local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/000077500000000000000000000000001474617255100227575ustar00rootroot00000000000000local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/agent_connection.rs000066400000000000000000000056501474617255100266500ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use local_agent_rs as la; use pyo3::prelude::*; // ----------------------------------------------------------------------------- use crate::{ future::future, AgentFeatures, Status, DEFAULT_TIMEOUT_IN_SECONDS, }; // ----------------------------------------------------------------------------- /// Represents an active connection to the LocalAgent server. /// /// One of these is needed per connection to a LocalAgent server. #[pyclass] pub struct AgentConnection { agent_connection: la::AgentConnection, } impl AgentConnection { /// Creates a new AgentConnection, dont use this directly, use /// AgentConnector::connect instead. pub(crate) fn new(agent_connection: la::AgentConnection) -> Self { Self { agent_connection } } } #[pymethods] impl AgentConnection { /// Requests the status of the local agent. /// /// This returns right away, and the result can be read later using the /// read method. #[pyo3(signature = (timeout_in_seconds=DEFAULT_TIMEOUT_IN_SECONDS))] pub fn request_status<'p>( &self, py: Python<'p>, timeout_in_seconds: u64, ) -> PyResult> { let agent_connection = self.agent_connection.clone(); future(py, async move { agent_connection.request_status(timeout_in_seconds).await?; Ok(()) }) } /// Makes a new feature request from the local agent. /// /// This returns right away, and the result can be read later using the /// read method. #[pyo3(signature = (features, timeout_in_seconds=DEFAULT_TIMEOUT_IN_SECONDS))] pub fn request_features<'p>( &self, py: Python<'p>, features: AgentFeatures, timeout_in_seconds: u64, ) -> PyResult> { let agent_connection = self.agent_connection.clone(); future(py, async move { agent_connection .request_features(features.into(), timeout_in_seconds) .await?; Ok(()) }) } /// Closes the local agent connection. #[pyo3(signature = (timeout_in_seconds=DEFAULT_TIMEOUT_IN_SECONDS))] pub fn close<'p>( &self, py: Python<'p>, timeout_in_seconds: u64, ) -> PyResult> { let agent_connection = self.agent_connection.clone(); future(py, async move { agent_connection.close(timeout_in_seconds).await?; Ok(()) }) } /// Reads the local agent response. #[pyo3()] pub fn read<'p>(&self, py: Python<'p>) -> PyResult> { let agent_connection = self.agent_connection.clone(); future(py, async move { Ok(Status::from(agent_connection.read().await?)) }) } } local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/agent_connector.rs000066400000000000000000000047661474617255100265120ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use pyo3::prelude::*; // ----------------------------------------------------------------------------- use local_agent_rs as la; // ----------------------------------------------------------------------------- use crate::{future::future, AgentConnection, DEFAULT_TIMEOUT_IN_SECONDS}; #[pyclass] /// Creator of AgentConnections. /// see AgentConnector::connect pub struct AgentConnector {} #[pymethods] impl AgentConnector { /// Creates a new AgentConnector. #[new] pub fn new() -> PyResult { Ok(Self {}) } /// Connects to the LocalAgent server. /// /// This is an async function, and will return a future that will resolve /// to an AgentConnection. /// /// # Arguments /// /// * `domain` - The name of the local agent server to connect to as a string. /// * `key` - The private key pks8 formatted in pem encoding as a string. /// * `cert` - The certificate in pem encoding as a string. /// * `timeout` - Optional timeout used to stablish the connection. /// #[pyo3(signature = (domain, key, cert, timeout_in_seconds=DEFAULT_TIMEOUT_IN_SECONDS))] pub fn connect<'p>( &self, py: Python<'p>, domain: String, key: String, cert: String, timeout_in_seconds: u64, ) -> PyResult> { future(py, async move { Ok(AgentConnection::new( la::AgentConnector::connect(la::ConnectParams { domain, key, cert, timeout_in_seconds, }) .await?, )) }) } /// Reads a string of json containing responses and returns an /// AgentConnection object, which behaves like a real connection. /// /// This is an async function, and will return a future that will resolve /// to an AgentConnection. /// /// # Arguments /// /// * `responses` - A string of json containing reponses. /// #[pyo3(signature = (responses))] pub fn playback<'p>( &self, py: Python<'p>, responses: String, ) -> PyResult> { future(py, async move { Ok(AgentConnection::new( la::AgentConnector::playback(&responses).await?, )) }) } } local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/agent_features.rs000066400000000000000000000066111474617255100263250ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use pyo3::prelude::*; // ----------------------------------------------------------------------------- use local_agent_rs as la; /// Contains all the features requested for a vpn connection #[pyclass] #[derive(Debug, Clone)] pub struct AgentFeatures { features: la::AgentFeatures, } impl std::convert::From for la::AgentFeatures { fn from(features: AgentFeatures) -> Self { features.features } } impl std::convert::From for AgentFeatures { fn from(features: la::AgentFeatures) -> Self { Self { features } } } #[pymethods] impl AgentFeatures { /// Creates a new AgentFeatures object, to be passed to /// AgentConnection::request_features. /// /// # Arguments /// /// * `netshield` - The netshield level. /// The netshield level to use for this session. /// 0 - No netshield. /// 1 - Block malware /// 2 - Block malware, trackers and adverts /// * `randomized_nat` - Whether to enable randomized NAT. /// Is random source port applied to outgoing NAT packets. /// * `split_tcp` - Whether to enable split TCP. /// Is the performance enhanced proxy enabled for this session ? /// * `port_forwarding` - Whether to enable port forwarding. /// * `fowarded_port` - Port where traffic is being forwarded when port forwarding is enabled. /// * `jail` - Whether to enable jailed mode. /// Jail the user (vpn tunnel established, but not communicating to the rest of internet) /// * `bouncing` - The bouncing level. /// The bouncing label selecting the outgoing source IP. /// #[new] #[pyo3(signature = ( netshield_level=None, randomized_nat=None, split_tcp=None, port_forwarding=None, forwarded_port=None, jail=None, bouncing=None, ))] pub fn new( netshield_level: Option, randomized_nat: Option, split_tcp: Option, port_forwarding: Option, forwarded_port: Option, jail: Option, bouncing: Option, ) -> PyResult { Ok(Self { features: la::AgentFeatures { netshield_level, randomized_nat, split_tcp, port_forwarding, forwarded_port, jail, bouncing, }, }) } #[getter] fn netshield_level(&self) -> PyResult> { Ok(self.features.netshield_level) } #[getter] fn randomized_nat(&self) -> PyResult> { Ok(self.features.randomized_nat) } #[getter] fn split_tcp(&self) -> PyResult> { Ok(self.features.split_tcp) } #[getter] fn port_forwarding(&self) -> PyResult> { Ok(self.features.port_forwarding) } #[getter] fn forwarded_port(&self) -> PyResult> { Ok(self.features.forwarded_port) } #[getter] fn jail(&self) -> PyResult> { Ok(self.features.jail) } #[getter] fn bouncing(&self) -> PyResult> { Ok(self.features.bouncing.clone()) } } local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/connection_details.rs000066400000000000000000000033441474617255100271750ustar00rootroot00000000000000pub use local_agent_rs as la; use pyo3::prelude::{pyclass, pymethods, PyResult}; use std::fmt; #[pyclass(get_all)] #[derive(Clone)] pub struct ConnectionDetails { pub device_ip: Option, pub device_country: Option, pub server_ipv4: Option, pub server_ipv6: Option, } impl fmt::Debug for ConnectionDetails { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let truncated_device_ip = if let Some(ip) = &self.device_ip { let last_dot_index: usize = ip.rfind('.').expect("IP address contains no dots.") + 1; let sliced_string = &ip[0..ip.len() - (ip.len() - last_dot_index)]; let mut truncated_string = String::from(sliced_string); truncated_string.push_str("xxx"); truncated_string } else { "None".to_string() }; write!( f, "ConnectionDetails {{ device_ip: {:?}, device_country: {:?}, server_ipv4: {:?}, server_ipv6: {:?} }}", truncated_device_ip, self.device_country, self.server_ipv4, self.server_ipv6 ) } } #[pymethods] impl ConnectionDetails { /// This method is used to convert the object to a string for easier /// debugging in Python. fn __str__(&self) -> PyResult { Ok(format!("{:?}", self)) } } impl From for ConnectionDetails { fn from(connection_details: la::ConnectionDetails) -> Self { ConnectionDetails { device_ip: connection_details.device_ip, device_country: connection_details.device_country, server_ipv4: connection_details.server_ipv4, server_ipv6: connection_details.server_ipv6, } } } local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/error.rs000066400000000000000000000054271474617255100244660ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use local_agent_rs as la; use crate::{ APIError, ExpiredCertificateError, LocalAgentError, PolicyAPIError, SyntaxAPIError, }; use pyo3::exceptions::PyTimeoutError; use pyo3::PyErr; use std::fmt::Write; use std::io::ErrorKind; use thiserror::Error; // ----------------------------------------------------------------------------- #[derive(Error, Debug)] pub enum Error { #[error("Local agent error: {0}")] LocalAgent(#[from] la::Error), #[error("IO error: {0}")] IO(#[from] std::io::Error), } fn convert_error_message_string(error: &T) -> String { let mut error_message = String::new(); writeln!(&mut error_message, "{:?}", &error) .expect("Unable to write to error string"); error_message } fn convert_to_default_error(error: &T) -> PyErr { LocalAgentError::new_err(convert_error_message_string(&error)) } const FEATURE_ERROR_RANGE: std::ops::Range = 86200..86300; impl std::convert::From for PyErr { fn from(err: Error) -> PyErr { match err { Error::LocalAgent(la::Error::Tokio(e)) if e.kind() == ErrorKind::InvalidData => { ExpiredCertificateError::new_err(convert_error_message_string( &e, )) } Error::LocalAgent(la::Error::Tokio(e)) if e.kind() == ErrorKind::TimedOut || e.kind() == ErrorKind::BrokenPipe => { PyTimeoutError::new_err(convert_error_message_string(&e)) } Error::LocalAgent(la::Error::TokioElapsed(e)) => { PyTimeoutError::new_err(convert_error_message_string(&e)) } Error::LocalAgent(la::Error::GetStatusError(e)) => { let error_message = convert_error_message_string(&e); // Check if the error is due to a policy error or an invalid // syntax error if FEATURE_ERROR_RANGE.contains(&e.code) { let error_type = e.code % 5; match error_type { 0 | 1 => return PolicyAPIError::new_err(error_message), 2 => return SyntaxAPIError::new_err(error_message), _ => (), } } // Otherwise, return a generic API error APIError::new_err(error_message) } Error::LocalAgent(e) => convert_to_default_error(&e), _ => convert_to_default_error(&err), } } } pub type Result = std::result::Result; local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/exception.rs000066400000000000000000000027331474617255100253300ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use pyo3::create_exception; use pyo3::prelude::*; // ----------------------------------------------------------------------------- create_exception!( local_agent, LocalAgentError, pyo3::exceptions::PyException, "General exception." ); create_exception!( local_agent, ExpiredCertificateError, LocalAgentError, "Raised when the passed certificate is expired during read from socket." ); create_exception!( local_agent, APIError, LocalAgentError, "Raised when an error message is read from socket." ); create_exception!( local_agent, SyntaxAPIError, APIError, "Raised when there is a syntax error using the api." ); create_exception!( local_agent, PolicyAPIError, APIError, "Raised when there is a policy error using the api." ); pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add( "LocalAgentError", m.py().get_type_bound::(), )?; m.add( "ExpiredCertificateError", m.py().get_type_bound::(), )?; m.add("APIError", m.py().get_type_bound::())?; m.add("SyntaxAPIError", m.py().get_type_bound::())?; m.add("PolicyAPIError", m.py().get_type_bound::())?; Ok(()) } local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/future.rs000066400000000000000000000014061474617255100246400ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use crate::Result; use pyo3::IntoPy; /// Converts a rust future into a python future, making sure to convert errors /// into Python exceptions. /// /// This is necessary as async {} blocks in Rust do not have a way to specify /// their return type. pub fn future( py: pyo3::Python, work: W, ) -> pyo3::PyResult> where W: std::future::Future> + Send + 'static, R: IntoPy>, { pyo3_asyncio_0_21::tokio::future_into_py(py, async move { work.await.map_err(pyo3::PyErr::from) }) } local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/lib.rs000066400000000000000000000031411474617255100240720ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use pyo3::prelude::*; // ----------------------------------------------------------------------------- mod agent_connection; mod agent_connector; mod agent_features; mod connection_details; mod error; mod exception; mod future; mod listener; mod reason; mod state; mod status; const DEFAULT_TIMEOUT_IN_SECONDS: u64 = 10; // ----------------------------------------------------------------------------- pub use agent_connection::AgentConnection; pub use agent_connector::AgentConnector; pub use agent_features::AgentFeatures; pub use connection_details::ConnectionDetails; pub use error::{Error, Result}; pub use exception::*; pub use listener::Listener; pub use reason::{Reason, ReasonCode}; pub use state::State; pub use status::Status; fn init_logger() { env_logger::init(); } #[pymodule] /// This is the entry point for the python module. fn local_agent(m: &Bound<'_, PyModule>) -> PyResult<()> { // Register the exceptions exception::register(m)?; // Add the AgentConnection and AgentConnector classes to the module. m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; // Start the logger when the module is returned. init_logger(); Ok(()) } local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/listener.rs000066400000000000000000000072621474617255100251610ustar00rootroot00000000000000use crate::{AgentFeatures, future::future, DEFAULT_TIMEOUT_IN_SECONDS, Status, error::Error}; use local_agent_rs as la; use pyo3::prelude::*; use pyo3::types::PyType; #[pyclass] pub struct Listener { listener: la::Listener, } #[pymethods] impl Listener { /// Starts the local agent listener. /// /// This is an async function, and will return a future that will resolve /// to a Listener. /// /// # Arguments /// /// * `domain` - The name of the local agent server to connect to as a string. /// * `key` - The private key pks8 formatted in pem encoding as a string. /// * `cert` - The certificate in pem encoding as a string. /// * `timeout_in_seconds` - Optional timeout used to stablish the connection. /// #[classmethod] #[pyo3(signature = (domain, key, cert, timeout_in_seconds=DEFAULT_TIMEOUT_IN_SECONDS))] pub fn connect<'p>( _cls: &Bound<'_, PyType>, py: Python<'p>, domain: String, key: String, cert: String, timeout_in_seconds: u64, ) -> PyResult> { future(py, async move { let connection_params = la::ConnectParams { domain, key, cert, timeout_in_seconds, }; Ok(Listener { listener: la::Listener::connect(connection_params).await? }) }) } /// Starts listening for local agent status updates. /// /// # Arguments /// /// * `status_callback` - Function that will be called with the new local agent status as parameter. /// * `error_callback` - Function that will be called with the local agent error as parameter, when they occur. pub fn listen<'p>( &self, py: Python<'p>, status_callback: PyObject, error_callback: PyObject, ) -> PyResult> { let listener = self.listener.clone(); let callback = move |result: la::Result| { let py_result: PyResult = result.map(Status::from).map_err(|error| Error::LocalAgent(error).into()); Python::with_gil( |py| { let cb_result = match py_result { Ok(response) => status_callback.call1(py, (response,)), Err(error) => error_callback.call1(py, (error,)), }; if let Err(error) = cb_result { log::error!("Error calling callback: {:?}", error.value_bound(py).to_string()); } } ); Ok(()) }; future(py, async move { listener .listen(callback) .await?; Ok(()) }) } /// Requests connection features. /// /// This method is expected to be called while listening to new agent statuses /// via the `listen()` method, and returns as soon as the request is done. /// The result is eventually sent via the `status_callback` passed to the /// `listen()` method. /// /// # Arguments /// /// * `features`: The requested features. /// * `timeout`: Amount of seconds before the request times out. #[pyo3(signature = (features, timeout_in_seconds=DEFAULT_TIMEOUT_IN_SECONDS))] pub fn request_features<'p>( &self, py: Python<'p>, features: AgentFeatures, timeout_in_seconds: u64, ) -> PyResult> { let listener = self.listener.clone(); future(py, async move { listener .request_features(features.into(), timeout_in_seconds) .await?; Ok(()) }) } } local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/reason.rs000066400000000000000000000065521474617255100246240ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- pub use local_agent_rs as la; use pyo3::prelude::*; #[pyclass] #[derive(Clone, Debug)] #[allow(non_camel_case_types)] pub enum ReasonCode { UNKNOWN, GUEST_SESSION, RESTRICTED_SERVER, BAD_CERT_SIGNATURE, CERT_NOT_PROVIDED, CERTIFICATE_EXPIRED, CERTIFICATE_REVOKED, MAX_SESSIONS_UNKNOWN, MAX_SESSIONS_FREE, MAX_SESSIONS_BASIC, MAX_SESSIONS_PLUS, MAX_SESSIONS_VISIONARY, MAX_SESSIONS_PRO, KEY_USED_MULTIPLE_TIMES, SERVER_ERROR, POLICY_VIOLATION_LOW_PLAN, POLICY_VIOLATION_DELINQUENT, USER_TORRENT_NOT_ALLOWED, USER_BAD_BEHAVIOR, } impl From for ReasonCode { fn from(reason: i32) -> Self { match reason { la::REASON_CODE_UNKNOWN => ReasonCode::UNKNOWN, la::REASON_CODE_GUEST_SESSION => ReasonCode::GUEST_SESSION, la::REASON_CODE_RESTRICTED_SERVER => ReasonCode::RESTRICTED_SERVER, la::REASON_CODE_BAD_CERT_SIGNATURE => { ReasonCode::BAD_CERT_SIGNATURE } la::REASON_CODE_CERT_NOT_PROVIDED => ReasonCode::CERT_NOT_PROVIDED, la::REASON_CODE_CERTIFICATE_EXPIRED => { ReasonCode::CERTIFICATE_EXPIRED } la::REASON_CODE_CERTIFICATE_REVOKED => { ReasonCode::CERTIFICATE_REVOKED } la::REASON_CODE_MAX_SESSIONS_UNKNOWN => { ReasonCode::MAX_SESSIONS_UNKNOWN } la::REASON_CODE_MAX_SESSIONS_FREE => ReasonCode::MAX_SESSIONS_FREE, la::REASON_CODE_MAX_SESSIONS_BASIC => { ReasonCode::MAX_SESSIONS_BASIC } la::REASON_CODE_MAX_SESSIONS_PLUS => ReasonCode::MAX_SESSIONS_PLUS, la::REASON_CODE_MAX_SESSIONS_VISIONARY => { ReasonCode::MAX_SESSIONS_VISIONARY } la::REASON_CODE_MAX_SESSIONS_PRO => ReasonCode::MAX_SESSIONS_PRO, la::REASON_CODE_KEY_USED_MULTIPLE_TIMES => { ReasonCode::KEY_USED_MULTIPLE_TIMES } la::REASON_CODE_SERVER_ERROR => ReasonCode::SERVER_ERROR, la::REASON_CODE_POLICY_VIOLATION_LOW_PLAN => { ReasonCode::POLICY_VIOLATION_LOW_PLAN } la::REASON_CODE_POLICY_VIOLATION_DELINQUENT => { ReasonCode::POLICY_VIOLATION_DELINQUENT } la::REASON_CODE_USER_TORRENT_NOT_ALLOWED => { ReasonCode::USER_TORRENT_NOT_ALLOWED } la::REASON_CODE_USER_BAD_BEHAVIOR => ReasonCode::USER_BAD_BEHAVIOR, _ => ReasonCode::UNKNOWN, } } } #[pyclass(get_all)] #[derive(Clone, Debug)] pub struct Reason { pub code: ReasonCode, pub is_final: bool, pub description: String, } #[pymethods] impl Reason { /// This method is used to convert the object to a string for easier /// debugging in Python. fn __str__(&self) -> PyResult { Ok(format!("{:?}", self)) } } impl From for Reason { fn from(reason: la::Reason) -> Self { Reason { code: ReasonCode::from(reason.code), is_final: reason.is_final, description: reason.description, } } } local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/state.rs000066400000000000000000000014601474617255100244460ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- pub use local_agent_rs as la; use pyo3::prelude::*; #[pyclass] #[derive(Clone, Debug)] #[allow(non_camel_case_types)] pub enum State { CONNECTED, HARD_JAILED } #[pymethods] impl State { // This method is used to convert the object to a string for easier /// debugging in Python. fn __str__(&self) -> PyResult { Ok(format!("{:?}", self)) } } impl std::convert::From for State { fn from(state: la::State) -> Self { match state { la::State::Connected => State::CONNECTED, la::State::HardJailed => State::HARD_JAILED, } } } local-agent-rs-1.4.3/python-proton-vpn-local-agent/src/status.rs000066400000000000000000000023571474617255100246570ustar00rootroot00000000000000// ----------------------------------------------------------------------------- // Copyright (c) 2024 Proton AG // ----------------------------------------------------------------------------- use crate::{ connection_details::ConnectionDetails, reason::Reason, state::State, AgentFeatures, }; pub use local_agent_rs as la; use pyo3::prelude::*; #[pyclass] #[derive(Clone, Debug)] pub struct Status { #[pyo3(get)] state: State, #[pyo3(get)] reason: Option, #[pyo3(get)] features: Option, #[pyo3(get)] connection_details: Option, } #[pymethods] impl Status { /// This method is used to convert the Status object to a string for easier /// debugging in Python. fn __str__(&self) -> PyResult { Ok(format!("{:?}", self)) } } impl std::convert::From for Status { fn from(status: la::StatusMessage) -> Self { Status { state: State::from(status.state), reason: status.reason.map(Reason::from), features: status.features.map(AgentFeatures::from), connection_details: status .connection_details .map(ConnectionDetails::from), } } } local-agent-rs-1.4.3/python-proton-vpn-local-agent/tests/000077500000000000000000000000001474617255100233325ustar00rootroot00000000000000local-agent-rs-1.4.3/python-proton-vpn-local-agent/tests/test_errors.py000066400000000000000000000051751474617255100262670ustar00rootroot00000000000000import pytest import json FEATURES = 86200 ERROR_CODES = [ # Feature request errors (86200, 'Unknown feature requested'), (86201, 'Bad message syntax'), (86202, 'Server session doesn\'t match'), (86203, 'Server session error (not found)'), (86210, 'Netshield level cannot be set - policy'), (86211, 'Netshield level cannot be set - system error'), (86212, 'Netshield level cannot be set - invalid level'), (86215, 'Bouncing cannot be set - policy'), (86216, 'Bouncing cannot be set - system error'), (86217, 'Bouncing cannot be set - invalid name'), (86220, 'Port forwarding cannot be set - policy'), (86221, 'Port forwarding cannot be set - system error'), (86222, 'Port forwarding cannot be set - invalid syntax'), (86223, 'Port forwarding cannot be set - not available on this server'), (86225, 'Non randomized nat cannot be set - policy'), (86226, 'Non randomized nat cannot be set - system error'), (86227, 'Non randomized nat cannot be set - invalid syntax'), (86230, 'Split TCP cannot be set - policy'), (86231, 'Split TCP cannot be set - system error'), (86232, 'Split TCP cannot be set - invalid syntax'), (86236, 'Soft Jail cannot be set - system error'), (86237, 'Soft Jail cannot be set - invalid syntax'), (86240, 'SafeMode cannot be set - policy'), (86241, 'SafeMode cannot be set - system error'), (86242, 'SafeMode cannot be set - invalid syntax'), (86250, 'Conflict between remote bouncing and non-randomized NAT'), (86251, 'Conflict between remote bouncing and no split tcp'), (86252, 'Conflict between remote bouncing and port forwarding'), # Hardjailed (86154, 'Bad user behavior'), ] @pytest.mark.asyncio async def test_error_codes(): def responses(): for code, description in ERROR_CODES: yield [ 0, { "error": { "code": code, "description": description } } ] from proton.vpn.local_agent import (AgentConnector, PolicyAPIError, SyntaxAPIError, APIError) connection = await AgentConnector().playback(json.dumps(list(responses()))) for code, _ in ERROR_CODES: try: await connection.read() except PolicyAPIError: assert code >= FEATURES and (code % 5 == 0 or code % 5 == 1) except SyntaxAPIError: assert code >= FEATURES and (code % 5 == 2) except APIError: assert ((code >= FEATURES and (code % 5 == 3 or code % 5 == 4)) or code < FEATURES) local-agent-rs-1.4.3/python-proton-vpn-local-agent/tests/test_local_agent_python_bindings.py000066400000000000000000000003621474617255100324720ustar00rootroot00000000000000def test_local_agent_python_bindings(): from proton.vpn.local_agent import AgentConnection assert AgentConnection from proton.vpn.local_agent import State assert State.CONNECTED from proton.vpn.local_agent import Status local-agent-rs-1.4.3/python-proton-vpn-local-agent/versions.yml000066400000000000000000000140371474617255100245700ustar00rootroot00000000000000version: 1.4.3 time: 2025/01/28 13:55 author: Josep Llaneras email: josep.llaneras@proton.ch urgency: low stability: unstable description: - Handle socket disconnection as a timeout error --- version: 1.4.2 time: 2025/01/28 13:00 author: Alexandru Cheltuitor email: alexandru.cheltuitor@proton.ch urgency: low stability: unstable description: - Ensure logger is automatically initialized. --- version: 1.4.1 time: 2025/01/27 12:00 author: Alexandru Cheltuitor email: alexandru.cheltuitor@proton.ch urgency: low stability: unstable description: - Handle PF refresh and errors (Alexandru Cheltuitor, Josep Llaneras). --- version: 1.4.0 time: 2025/01/21 14:44 author: Josep Llaneras email: josep.llaneras@proton.ch urgency: low stability: unstable description: - Integrate port forwarding into listener --- version: 1.3.0 time: 2025/01/16 12:00 author: Alexandru Cheltuitor email: alexandru.cheltuitor@proton.ch urgency: low stability: unstable description: - Refactor API (Alexandru Cheltuitor, Josep Llaneras). --- version: 1.2.1 time: 2024/12/17 21:34 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Added port forwarding module to local-agent-rs --- version: 1.2.0 time: 2024/11/19 13:00 author: Alexandru Cheltuitor email: alexandru.cheltuitor@proton.ch urgency: low stability: unstable description: - Drop Ubuntu 20.04 support. --- version: 1.1.5 time: 2024/11/13 16:49 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Added semgrep scanning to CI --- version: 1.1.4 time: 2024/10/21 13:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Build rpm package.spec file from a template. --- version: 1.1.3 time: 2024/10/14 13:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Fixes for building with rpmbuild on fedora 41. --- version: 1.1.2 time: 2024/10/09 13:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Build changelog automatically --- version: 1.1.1 time: 2024/10/03 13:00 author: Alexandru Cheltuitor email: alexandru.cheltuitor@proton.ch urgency: low stability: unstable description: - Truncate IP. --- version: 1.1.0 time: 2024/10/02 13:00 author: Alexandru Cheltuitor email: alexandru.cheltuitor@proton.ch urgency: low stability: unstable description: - Implement and expose connection details. --- version: 1.0.0 time: 2024/09/17 17:47 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Introduced the APIError, SyntaxAPIError and PolicyAPIError exceptions. - Reworked the error handling at the same time. --- version: 0.10.0 time: 2024/08/12 17:47 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Fix deadlock when doing requests and reads at the same time. --- version: 0.9.0 time: 2024/08/9 19:26 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - AgentConnection::read now also returns what features have been set. - Python ErrorMessage exception is raised when reading responses from Local Agent (Josep Llaneras) --- version: 0.8.0 time: 2024/08/9 09:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Adds support for AgentConnection::request_features, and AgentFeatures object. --- version: 0.7.0 time: 2024/08/9 09:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Adds a playback mechanism which makes it possible to test responses from the server. --- version: 0.6.0 time: 2024/08/07 16:00 author: Josep Llaneras email: josep.llaneras@proton.ch urgency: low stability: unstable description: - Configure TCP connection keep alive. --- version: 0.5.0 time: 2024/08/1 09:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Report an error if certificate is expired during session. --- version: 0.4.0 time: 2024/07/23 09:00 author: Josep Llaneras email: josep.llaneras@proton.ch urgency: low stability: unstable description: - Add method to read asynchronous local agent messages. --- version: 0.3.1 time: 2024/07/18 12:50 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Add a timeout for the tls handshake --- version: 0.3.0 time: 2024/07/17 11:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Add timeouts to the API. - Make AgentConnection::get_status async. - Added AgentConnection::close to allow us to explicitly close a connection. --- version: 0.2.0 time: 2024/07/17 09:00 author: Alexandru Cheltuitor email: alexandru.cheltuitor@proton.ch urgency: low stability: unstable description: - Make AgentConnector::connect raise exception when establishing a new LA connection with an expired certificate. --- version: 0.1.2 time: 2024/07/11 15:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Make AgentConnection::get_status async in the python bindings. --- version: 0.1.1 time: 2024/07/11 15:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Add unit test framework for rust. --- version: 0.1.0 time: 2024/07/5 10:00 author: Alexandru Cheltuitor email: alexandru.cheltuitor@proton.ch urgency: low stability: unstable description: - Implement get_status() so that it returns connection status. --- version: 0.0.3 time: 2024/07/2 16:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Add LocalAgentConnectionError and LocalAgentBaseError. Currently all errors 'except panics' are LocalAgentConnectionErrors. --- version: 0.0.2 time: 2024/06/28 17:30 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Support for asyncio --- version: 0.0.1 time: 2024/05/31 11:00 author: Luke Titley email: luke.titley@proton.ch urgency: low stability: unstable description: - Initial release of stub local agent. local-agent-rs-1.4.3/rustfmt.toml000066400000000000000000000000601474617255100167400ustar00rootroot00000000000000max_width = 80 hard_tabs = false tab_spaces = 4 local-agent-rs-1.4.3/tests/000077500000000000000000000000001474617255100155055ustar00rootroot00000000000000local-agent-rs-1.4.3/tests/load_secrets.py000066400000000000000000000033651474617255100205350ustar00rootroot00000000000000# You need to copy your secrets from the keyring proton vpn entry you are using # to log in, into a file called 'secrets.json'. # # Then you need to add a 'server' key entry to that file, with the domain name # of the server that will contain the local agent server. # # You can find the domain name by looking into the serverlist cache file. import json import base64 import os import cryptography from cryptography.hazmat.primitives.serialization import Encoding, \ PrivateFormat from cryptography.hazmat.primitives import serialization from dataclasses import dataclass SECRETS_FILENAME = "secrets.json" path = os.path.abspath(__file__).split("/") path[-1] = SECRETS_FILENAME FILEPATH = "/".join(path) @dataclass class Secrets: certificate: str server_domain: str private_key: str def load_secrets(): # Load the private key and certificate from secrets.json # # You need to make secrets.json yourself, buy copying the secrets from your # ProtonVPN keyring. with open(FILEPATH, "r", encoding="utf-8") as f: secrets = json.load(f) # The certificate certificate = secrets["vpn"]["certificate"]["Certificate"] server_domain = secrets["server"] # The private key private_key = base64.b64decode(secrets["vpn"]["secrets"]["ed25519_privatekey"]) private_key = cryptography.hazmat.primitives.asymmetric\ .ed25519.Ed25519PrivateKey.from_private_bytes(private_key) private_key = private_key.private_bytes( encoding=Encoding.PEM, format=PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ).decode("ascii") return Secrets(certificate, server_domain, private_key) local-agent-rs-1.4.3/tests/local_agent.so000077700000000000000000000000001474617255100404502../python-proton-vpn-local-agent/target/release/libpython_proton_vpn_local_agent.soustar00rootroot00000000000000local-agent-rs-1.4.3/tests/test.sh000077500000000000000000000004101474617255100170160ustar00rootroot00000000000000 # Builds and runs the tests from this directory. set -e SCRIPT_DIR=$(dirname "$(readlink -f "$0")") pushd $SCRIPT_DIR/../python-proton-vpn-local-agent cargo build --release popd pushd $SCRIPT_DIR ./test_connection.py ./test_playback.py ./test_listener.py popd local-agent-rs-1.4.3/tests/test_connection.py000077500000000000000000000035441474617255100212660ustar00rootroot00000000000000#!/usr/bin/env python3 ''' This tests that local_agent.AgentConnection() can be used to connect to a VPN server. If no exception are raised during the call to AgentConnection.connect(), then the test is successful. Check instructions on load_secrets.py on how to set thing up to be able to run this test. For this test to work it's required to be connected to be connected to a server, and passing a domain that matches that server to the `secrets.json` file. ''' import asyncio import local_agent from load_secrets import load_secrets secrets = load_secrets() timeout = 1 # 1 second timeout async def do_read(connection): print("Waiting for read...") return await connection.read() # Connect to the VPN server async def make_test_connection(): local_agent.init() agent_connector = local_agent.AgentConnector() print() agent_connection = await agent_connector.connect( secrets.server_domain, secrets.private_key, secrets.certificate, timeout) print("Local agent connection established") # First connection we get a status straight away print("Read status") print(await agent_connection.read()) # Now request another status print("Request a new status") await agent_connection.request_status(timeout) print(await agent_connection.read()) wait_for_read = asyncio.create_task(do_read(agent_connection)) print("Requesting features") await asyncio.sleep(1) agent_connection.request_features( local_agent.AgentFeatures(netshield_level = 3) ) # Now get the latest status back. # We should have features in it. status = await wait_for_read print(status) # Now try to read the netshield level directly print(status.features.netshield_level) # Now we're done with the connection await agent_connection.close() # execute the coroutine asyncio.run(make_test_connection()) local-agent-rs-1.4.3/tests/test_listener.py000077500000000000000000000023371474617255100207530ustar00rootroot00000000000000#!/usr/bin/env python3 """ Tests that we can start a LA connection and listen for status changes. To be able to run this test you need to be connected to a VPN server and create the secrets.json file as documented in load_secrets.py. """ import asyncio from load_secrets import load_secrets from local_agent import Listener, AgentFeatures, ExpiredCertificateError def error_callback(error): print("\nError callback: ", error) def status_callback(status): print("\nStatus callback: ", status) async def main(): secrets = load_secrets() try: listener = await Listener.connect(secrets.server_domain, secrets.private_key, secrets.certificate) except ExpiredCertificateError: print("\nExpired certificate error") raise except TimeoutError: print("\nTimeout error") raise except Exception: print("\nGeneral error") raise future = listener.listen(status_callback, error_callback) await asyncio.sleep(1) await listener.request_features(AgentFeatures(netshield_level = 3)) await asyncio.sleep(3) future.cancel() try: await future except asyncio.CancelledError: print("Listener was stopped.") asyncio.run(main()) local-agent-rs-1.4.3/tests/test_playback.py000077500000000000000000000014351474617255100207120ustar00rootroot00000000000000#!/usr/bin/env python3 import asyncio import local_agent import json responses = [ [ 0, { "status" : { "state" : "connected" } } ], [ 1, { "status" : { "reason" : { "code" : 86111, "description" : "Max session reached for this plan", "final" : False }, "state" : "hard-jailed" } } ] ] # Connect to the VPN server async def make_test_connection(): agent_connection = await local_agent.AgentConnector().playback(json.dumps(responses)) print(await agent_connection.read()) print(await agent_connection.read()) await agent_connection.close() # execute the coroutine asyncio.run(make_test_connection())