mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
Remove metadata extraction from proxy
This commit is contained in:
284
codex-rs/Cargo.lock
generated
284
codex-rs/Cargo.lock
generated
@@ -193,6 +193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
@@ -655,6 +656,16 @@ dependencies = [
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asynk-strim"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52697735bdaac441a29391a9e97102c74c6ef0f9b60a40cf109b1b404e29d2f6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@@ -721,7 +732,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"matchit 0.8.4",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
@@ -1784,6 +1795,7 @@ name = "codex-network-proxy"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"clap",
|
||||
"codex-app-server-protocol",
|
||||
"codex-core",
|
||||
@@ -3037,6 +3049,12 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||
|
||||
[[package]]
|
||||
name = "endian-type"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2"
|
||||
|
||||
[[package]]
|
||||
name = "enum-as-inner"
|
||||
version = "0.6.1"
|
||||
@@ -3195,6 +3213,9 @@ name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fax"
|
||||
@@ -3304,13 +3325,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.1"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
|
||||
checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"nanorand",
|
||||
"spin",
|
||||
]
|
||||
|
||||
@@ -4727,6 +4748,12 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3eede3bdf92f3b4f9dc04072a9ce5ab557d5ec9038773bf9ffcd5588b3cc05b"
|
||||
|
||||
[[package]]
|
||||
name = "mcp-types"
|
||||
version = "0.0.0"
|
||||
@@ -4866,15 +4893,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanorand"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
@@ -5824,7 +5842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.14.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
@@ -5973,16 +5991,29 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
|
||||
dependencies = [
|
||||
"endian-type",
|
||||
"endian-type 0.1.2",
|
||||
"nibble_vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radix_trie"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a"
|
||||
dependencies = [
|
||||
"endian-type 0.2.0",
|
||||
"nibble_vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9abba26d2a4a60eb94a17224755035ce03f06fd1f3448915cbd8bf02dd838d"
|
||||
checksum = "66a8e02be6b50e4c35cbba44f15828fa716fa3048bb3b751b8dbda338983346f"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"base64",
|
||||
"opentelemetry-otlp",
|
||||
"pin-project-lite",
|
||||
"rama-core",
|
||||
"rama-crypto",
|
||||
@@ -5997,21 +6028,22 @@ dependencies = [
|
||||
"rama-tcp",
|
||||
"rama-tls-boring",
|
||||
"rama-tls-rustls",
|
||||
"rama-tower",
|
||||
"rama-ua",
|
||||
"rama-udp",
|
||||
"rama-unix",
|
||||
"rama-utils",
|
||||
"rama-ws",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-boring"
|
||||
version = "0.4.0"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6c52217ff947d630f3807cde9f8fe4cc69005b17a1e81fc99fa432cb3fb9c78"
|
||||
checksum = "288926585d0b8ed1b1dd278a31ea1367007ad0bd4263ca84810e10939c2398a3"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"foreign-types 0.5.0",
|
||||
@@ -6022,11 +6054,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-boring-sys"
|
||||
version = "0.4.0"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c7fd7057828ca9fa148c704f1bf0fff12f6367891212c9c0caad63ae17902a7"
|
||||
checksum = "421ebb40444a6d740f867a5055a710a73e7cd74b9b389583b45e9b29d1330465"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bindgen",
|
||||
"cmake",
|
||||
"fs_extra",
|
||||
@@ -6035,9 +6066,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-boring-tokio"
|
||||
version = "0.4.0"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b07c71d66f9fa0b52b02055b94c779e98fc27d228e67105c6e528cfd1f19a7a4"
|
||||
checksum = "913cf3d377b37ff903cd57c2f6133ba7da5b7e0fe94821dec83daa8a024bb6ce"
|
||||
dependencies = [
|
||||
"rama-boring",
|
||||
"rama-boring-sys",
|
||||
@@ -6046,11 +6077,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-core"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea03771324b3d27a4d5d450bdd288fdeac87638f7cdb5870f3d1da6503542483"
|
||||
checksum = "0b93751ab27c9d151e84c1100057eab3f2a6a1378bc31b62abd416ecb1847658"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"ahash",
|
||||
"asynk-strim",
|
||||
"bytes",
|
||||
"futures",
|
||||
"parking_lot",
|
||||
@@ -6058,16 +6090,19 @@ dependencies = [
|
||||
"rama-error",
|
||||
"rama-macros",
|
||||
"rama-utils",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tokio-graceful",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-crypto"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91965fc4cb4bc45bd2980f6388f97dbc955bcf164fdbd81fc0e9155c4e9abe2f"
|
||||
checksum = "d4ea5a793b2fe86a32e11a672d68378a18073701cce9b3f2b477b80b304711c5"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"base64",
|
||||
@@ -6082,10 +6117,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-dns"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7556de219395b9b170f9fdbff10ee6a1c022749207a2d013c0c89e197bbaae3c"
|
||||
checksum = "e340fef2799277e204260b17af01bc23604712092eacd6defe40167f304baed8"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"hickory-resolver",
|
||||
"rama-core",
|
||||
"rama-net",
|
||||
@@ -6096,15 +6132,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-error"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b3236ea868a50937223cbe56130b99725235c001ca8886cc01bf599736c9766"
|
||||
checksum = "3c452aba1beb7e29b873ff32f304536164cffcc596e786921aea64e858ff8f40"
|
||||
|
||||
[[package]]
|
||||
name = "rama-haproxy"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30cf7f5fe1af97475f2e4c7dc6320caca1db2c65336c8ce6513bf5790ca0f75f"
|
||||
checksum = "3ad683d7f8858bc04c6e62525f598e2289c1cfee6193b9d4e033805552fe3992"
|
||||
dependencies = [
|
||||
"rama-core",
|
||||
"rama-net",
|
||||
@@ -6114,51 +6150,50 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-http"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52279e9b6c364ac8db44209707544eac45a3b79c1953cb3cd3985272565d9818"
|
||||
checksum = "453d60af031e23af2d48995e41b17023f6150044738680508b63671f8d7417dd"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"async-compression",
|
||||
"base64",
|
||||
"bitflags 2.10.0",
|
||||
"chrono",
|
||||
"compression-codecs",
|
||||
"compression-core",
|
||||
"const_format",
|
||||
"csv",
|
||||
"flate2",
|
||||
"http 1.3.1",
|
||||
"http-range-header",
|
||||
"httpdate",
|
||||
"iri-string",
|
||||
"matchit",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"matchit 0.9.1",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"radix_trie 0.3.0",
|
||||
"rama-core",
|
||||
"rama-error",
|
||||
"rama-http-headers",
|
||||
"rama-http-types",
|
||||
"rama-net",
|
||||
"rama-ua",
|
||||
"rama-utils",
|
||||
"rand 0.9.2",
|
||||
"rawzip",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_html_form",
|
||||
"serde_json",
|
||||
"smol_str",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-http-backend"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f53a4fab6c60854a5d53dccdfdec7b86be697269d523adfff3ff13cca7455a9"
|
||||
checksum = "f3ff6a3c8ae690be8167e43777ba0bf6b0c8c2f6de165c538666affe2a32fd81"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"futures",
|
||||
"h2",
|
||||
"pin-project-lite",
|
||||
"rama-core",
|
||||
@@ -6175,38 +6210,40 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-http-core"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42d2a4dc0b90d0ff7a2b7eda9993b51c7ad234b343c700f480ff83f7dea46966"
|
||||
checksum = "3822be6703e010afec0bcfeb5dbb6e5a3b23ca5689d9b1215b66ce6446653b77"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"atomic-waker",
|
||||
"futures-channel",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"indexmap 2.12.0",
|
||||
"itoa",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"rama-core",
|
||||
"rama-http",
|
||||
"rama-http-types",
|
||||
"rama-net",
|
||||
"rama-utils",
|
||||
"slab",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
"tokio-util",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-http-headers"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c49d5a5f7c95eaffab07609767ed60d52f6c56e7c2115565311545e7141ecd23"
|
||||
checksum = "9d74fe0cd9bd4440827dc6dc0f504cf66065396532e798891dee2c1b740b2285"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"base64",
|
||||
"chrono",
|
||||
"const_format",
|
||||
"httpdate",
|
||||
"mime",
|
||||
"rama-core",
|
||||
"rama-error",
|
||||
"rama-http-types",
|
||||
@@ -6220,10 +6257,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-http-types"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "020137823b0d45de8eb78d261a45dc92a6d2cf763a948fc01ff922cd23eeabd3"
|
||||
checksum = "b6dae655a72da5f2b97cfacb67960d8b28c5025e62707b4c8c5f0c5c9843a444"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytes",
|
||||
"const_format",
|
||||
"fnv",
|
||||
"http 1.3.1",
|
||||
@@ -6242,30 +6281,29 @@ dependencies = [
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"smol_str",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-macros"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8531184c892978544f6799b132e2ff7c00ceaf8b7d68c2d693ad441d351cbf5"
|
||||
checksum = "ea18a110bcf21e35c5f194168e6914ccea45ffdd0fea51bc4b169fbeafef6428"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-net"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f2ca3d7c32de50880b81fc7d923fa3d9c9cf8d89c6557abdbd7e58758087926"
|
||||
checksum = "b28ee9e1e5d39264414b71f5c33e7fbb66b382c3fac456fe0daad39cf5509933"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"const_format",
|
||||
"flume",
|
||||
"hex",
|
||||
@@ -6276,14 +6314,13 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"psl",
|
||||
"radix_trie",
|
||||
"radix_trie 0.3.0",
|
||||
"rama-core",
|
||||
"rama-http-types",
|
||||
"rama-macros",
|
||||
"rama-utils",
|
||||
"serde",
|
||||
"sha2",
|
||||
"smol_str",
|
||||
"socket2 0.6.1",
|
||||
"tokio",
|
||||
"venndb",
|
||||
@@ -6291,9 +6328,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-proxy"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ec0bb1a048cececb6d1a1c0e093ae7e3081d291511a6c2cbdcef8a5975a3c12"
|
||||
checksum = "149eaf3134c30af80017182f3e659ad05d242e1ade8181ef8035b83859fafac7"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"base64",
|
||||
@@ -6308,9 +6345,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-socks5"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2aeb01a3889de2d2a30e2a9143ffd26e1ed805f04fc477b02883dc0b907533b2"
|
||||
checksum = "5468b263516daaf258de32542c1974b7cbe962363ad913dcb669f5d46db0ef3e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rama-core",
|
||||
@@ -6320,16 +6357,16 @@ dependencies = [
|
||||
"rama-udp",
|
||||
"rama-utils",
|
||||
"rand 0.9.2",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-tcp"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90f6b443f656ccf6d4d3cf5e288f73d5484f92c17dbe9836a1cdf13b4c9a4f67"
|
||||
checksum = "fe60cd604f91196b3659a1b28945add2e8b10bd0b4e6373c93d024fb3197704b"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"rama-core",
|
||||
"rama-dns",
|
||||
"rama-http-types",
|
||||
@@ -6341,10 +6378,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-tls-boring"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed51b8ae893be304f578b94702f92d564e5d293928349e92d6151d9ab78bf90"
|
||||
checksum = "def3d5d06d3ca3a2d2e4376cf93de0555cd9c7960f085bf77be9562f5c9ace8f"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"brotli",
|
||||
"flate2",
|
||||
"flume",
|
||||
@@ -6366,59 +6404,48 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-tls-rustls"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b544da527a10d09ff03d08a120adb0e7ea239fabcbf871863bb7f134d1d70b"
|
||||
checksum = "536d47f6b269fb20dffd45e4c04aa8b340698b3509326e3c36e444b4f33ce0d6"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"rama-core",
|
||||
"rama-http-types",
|
||||
"rama-net",
|
||||
"rama-utils",
|
||||
"rcgen",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-tower"
|
||||
version = "0.3.0-alpha.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2236eed00294ddc68befaaf565e6924d1918c97eaf63d71e2448d6ab961d52c"
|
||||
dependencies = [
|
||||
"rama-core",
|
||||
"rama-http-types",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"x509-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-ua"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e496c2926de78dd36d3190fe852b11e39260079b05590c531b187c997078818c"
|
||||
checksum = "d7abde8e7b428c80c5948885c1ee0492852a21d91844c8414a0de4a8a1d62262"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"itertools 0.14.0",
|
||||
"rama-core",
|
||||
"rama-http-headers",
|
||||
"rama-http-types",
|
||||
"rama-http",
|
||||
"rama-net",
|
||||
"rama-utils",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"serde_html_form",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-udp"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0460c8db3b802ffa1d1f91bd375ee431da3780d6625d2503891e881ab115339a"
|
||||
checksum = "36ed05e0ecac73e084e92a3a8b1fbf16fdae8958c506f0f0eada180a2d99eef4"
|
||||
dependencies = [
|
||||
"rama-core",
|
||||
"rama-net",
|
||||
@@ -6428,35 +6455,39 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rama-unix"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa5e3efecbd7ba29667df34c9a949ed36da1d7b4f8f7b43b3b45e6234bbd932f"
|
||||
checksum = "91acb16d571428ba4cece072dfab90d2667cdfa910a7b3cb4530c3f31542d708"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"rama-core",
|
||||
"rama-net",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-utils"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fd73071ddb078866fb7fca5def45bbfa12e7ea1cd819a0e3b701bf09ba2c72"
|
||||
checksum = "bf28b18ba4a57f8334d7992d3f8020194ea359b246ae6f8f98b8df524c7a14ef"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"rama-macros",
|
||||
"regex",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"smol_str",
|
||||
"tokio",
|
||||
"wildcard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rama-ws"
|
||||
version = "0.3.0-alpha.3"
|
||||
version = "0.3.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6d2cc376163a79bdb2b49e8caad99b6b9c204424f8307c52b04c6627fd8e0b"
|
||||
checksum = "300b2b6ba51381d1a6918d1142879a8314588f7cf24669f6ba8439d9d19ab486"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"rama-core",
|
||||
@@ -6898,15 +6929,6 @@ dependencies = [
|
||||
"security-framework 3.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.12.0"
|
||||
@@ -6950,7 +6972,7 @@ dependencies = [
|
||||
"log",
|
||||
"memchr",
|
||||
"nix 0.28.0",
|
||||
"radix_trie",
|
||||
"radix_trie 0.2.1",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.1.14",
|
||||
"utf8parse",
|
||||
@@ -7346,9 +7368,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_html_form"
|
||||
version = "0.2.8"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f"
|
||||
checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"indexmap 2.12.0",
|
||||
@@ -7618,6 +7640,9 @@ name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
@@ -8580,22 +8605,36 @@ dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-serde"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9268,6 +9307,15 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
|
||||
|
||||
[[package]]
|
||||
name = "wildcard"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9b0540e91e49de3817c314da0dd3bc518093ceacc6ea5327cb0e1eb073e5189"
|
||||
dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wildmatch"
|
||||
version = "2.6.1"
|
||||
|
||||
@@ -17,6 +17,7 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
@@ -28,7 +29,7 @@ time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["fmt"] }
|
||||
rama = { version = "=0.3.0-alpha.3", default-features = false, features = ["http-full", "proxy-full", "socks5", "rustls"] }
|
||||
rama = { version = "=0.3.0-alpha.4", default-features = false, features = ["http-full", "proxy-full", "socks5", "rustls"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
@@ -67,6 +67,13 @@ cargo run -p codex-network-proxy -- init
|
||||
cargo run -p codex-network-proxy --
|
||||
```
|
||||
|
||||
Optional flags:
|
||||
|
||||
```bash
|
||||
# Enable SOCKS5 UDP associate support (off by default).
|
||||
cargo run -p codex-network-proxy -- --enable-socks5-udp
|
||||
```
|
||||
|
||||
### 4) Point a client at it
|
||||
|
||||
For HTTP(S) traffic:
|
||||
@@ -97,6 +104,45 @@ In "limited" mode, only `GET`, `HEAD`, and `OPTIONS` are allowed. In addition, H
|
||||
requires MITM to be enabled to allow read-only HTTPS; otherwise the proxy blocks CONNECT with
|
||||
reason `mitm_required`.
|
||||
|
||||
## Library API
|
||||
|
||||
`codex-network-proxy` can be embedded as a library with a thin API:
|
||||
|
||||
```rust
|
||||
use codex_network_proxy::{NetworkProxy, NetworkDecision, NetworkPolicyRequest};
|
||||
|
||||
let proxy = NetworkProxy::builder()
|
||||
.http_addr("127.0.0.1:8080".parse()?)
|
||||
.socks_addr("127.0.0.1:1080".parse()?)
|
||||
.admin_addr("127.0.0.1:9000".parse()?)
|
||||
.policy_decider(|request: NetworkPolicyRequest| async move {
|
||||
// Example: auto-allow when exec policy already approved a command prefix.
|
||||
if let Some(command) = request.command.as_deref() {
|
||||
if command.starts_with("curl ") {
|
||||
return NetworkDecision::Allow;
|
||||
}
|
||||
}
|
||||
NetworkDecision::Deny {
|
||||
reason: "policy_denied".to_string(),
|
||||
}
|
||||
})
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let handle = proxy.run().await?;
|
||||
handle.shutdown().await?;
|
||||
```
|
||||
|
||||
### Policy hook (exec-policy mapping)
|
||||
|
||||
The proxy exposes a policy hook (`NetworkPolicyDecider`) that can override allowlist-only blocks.
|
||||
It receives `command` and `exec_policy_hint` fields when supplied by the embedding app. This lets
|
||||
core map exec approvals to network access, e.g. if a user already approved `curl *` for a session,
|
||||
the decider can auto-allow network requests originating from that command.
|
||||
|
||||
**Important:** Explicit deny rules still win. The decider only gets a chance to override
|
||||
`not_allowed` (allowlist misses), not `denied` or `not_allowed_local`.
|
||||
|
||||
## Admin API
|
||||
|
||||
The admin API is a small HTTP server intended for debugging and runtime adjustments.
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
use crate::config::NetworkMode;
|
||||
use crate::mitm;
|
||||
use crate::network_policy::NetworkDecision;
|
||||
use crate::network_policy::NetworkPolicyDecider;
|
||||
use crate::network_policy::NetworkPolicyRequest;
|
||||
use crate::network_policy::NetworkProtocol;
|
||||
use crate::network_policy::evaluate_host_policy;
|
||||
use crate::policy::normalize_host;
|
||||
use crate::responses::blocked_header_value;
|
||||
use crate::state::AppState;
|
||||
use crate::state::BlockedRequest;
|
||||
use anyhow::Context as _;
|
||||
use anyhow::Result;
|
||||
use rama::Context;
|
||||
use rama::Layer;
|
||||
use rama::Service;
|
||||
use rama::extensions::ExtensionsMut;
|
||||
use rama::extensions::ExtensionsRef;
|
||||
use rama::http::Body;
|
||||
use rama::http::Request;
|
||||
use rama::http::Response;
|
||||
@@ -20,7 +26,7 @@ use rama::http::layer::upgrade::UpgradeLayer;
|
||||
use rama::http::layer::upgrade::Upgraded;
|
||||
use rama::http::matcher::MethodMatcher;
|
||||
use rama::http::server::HttpServer;
|
||||
use rama::layer::AddExtensionLayer;
|
||||
use rama::layer::AddInputExtensionLayer;
|
||||
use rama::net::http::RequestContext;
|
||||
use rama::net::proxy::ProxyTarget;
|
||||
use rama::net::stream::SocketInfo;
|
||||
@@ -35,7 +41,11 @@ use tracing::error;
|
||||
use tracing::info;
|
||||
use tracing::warn;
|
||||
|
||||
pub async fn run_http_proxy(state: Arc<AppState>, addr: SocketAddr) -> Result<()> {
|
||||
pub async fn run_http_proxy(
|
||||
state: Arc<AppState>,
|
||||
addr: SocketAddr,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
) -> Result<()> {
|
||||
let listener = TcpListener::build()
|
||||
.bind(addr)
|
||||
.await
|
||||
@@ -51,39 +61,40 @@ pub async fn run_http_proxy(state: Arc<AppState>, addr: SocketAddr) -> Result<()
|
||||
(
|
||||
UpgradeLayer::new(
|
||||
MethodMatcher::CONNECT,
|
||||
service_fn(http_connect_accept),
|
||||
service_fn({
|
||||
let policy_decider = policy_decider.clone();
|
||||
move |req| http_connect_accept(policy_decider.clone(), req)
|
||||
}),
|
||||
service_fn(http_connect_proxy),
|
||||
),
|
||||
RemoveResponseHeaderLayer::hop_by_hop(),
|
||||
RemoveRequestHeaderLayer::hop_by_hop(),
|
||||
)
|
||||
.into_layer(service_fn(http_plain_proxy)),
|
||||
.into_layer(service_fn({
|
||||
let policy_decider = policy_decider.clone();
|
||||
move |req| http_plain_proxy(policy_decider.clone(), req)
|
||||
})),
|
||||
);
|
||||
|
||||
info!("HTTP proxy listening on {addr}");
|
||||
|
||||
listener
|
||||
.serve(AddExtensionLayer::new(state).into_layer(http_service))
|
||||
.serve(AddInputExtensionLayer::new(state).into_layer(http_service))
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn http_connect_accept<S>(
|
||||
mut ctx: Context<S>,
|
||||
req: Request,
|
||||
) -> Result<(Response, Context<S>, Request), Response>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let app_state = ctx
|
||||
async fn http_connect_accept(
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
mut req: Request,
|
||||
) -> Result<(Response, Request), Response> {
|
||||
let app_state = req
|
||||
.extensions()
|
||||
.get::<Arc<AppState>>()
|
||||
.cloned()
|
||||
.ok_or_else(|| text_response(StatusCode::INTERNAL_SERVER_ERROR, "missing state"))?;
|
||||
|
||||
let authority = match ctx
|
||||
.get_or_try_insert_with_ctx::<RequestContext, _>(|ctx| (ctx, &req).try_into())
|
||||
.map(|ctx| ctx.authority.clone())
|
||||
{
|
||||
let authority = match RequestContext::try_from(&req).map(|ctx| ctx.host_with_port()) {
|
||||
Ok(authority) => authority,
|
||||
Err(err) => {
|
||||
warn!("CONNECT missing authority: {err}");
|
||||
@@ -91,15 +102,25 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let host = normalize_host(&authority.host().to_string());
|
||||
let host = normalize_host(&authority.host.to_string());
|
||||
if host.is_empty() {
|
||||
return Err(text_response(StatusCode::BAD_REQUEST, "invalid host"));
|
||||
}
|
||||
|
||||
let client = client_addr(&ctx);
|
||||
let client = client_addr(&req);
|
||||
|
||||
match app_state.host_blocked(&host, authority.port()).await {
|
||||
Ok((true, reason)) => {
|
||||
let request = NetworkPolicyRequest::new(
|
||||
NetworkProtocol::HttpsConnect,
|
||||
host.clone(),
|
||||
authority.port,
|
||||
client.clone(),
|
||||
Some("CONNECT".to_string()),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
match evaluate_host_policy(&app_state, policy_decider.as_ref(), &request).await {
|
||||
Ok(NetworkDecision::Deny { reason }) => {
|
||||
let _ = app_state
|
||||
.record_blocked(BlockedRequest::new(
|
||||
host.clone(),
|
||||
@@ -114,7 +135,7 @@ where
|
||||
warn!("CONNECT blocked (client={client}, host={host}, reason={reason})");
|
||||
return Err(blocked_text(&reason));
|
||||
}
|
||||
Ok((false, _)) => {
|
||||
Ok(NetworkDecision::Allow) => {
|
||||
let client = client.as_deref().unwrap_or_default();
|
||||
info!("CONNECT allowed (client={client}, host={host})");
|
||||
}
|
||||
@@ -160,10 +181,10 @@ where
|
||||
return Err(blocked_text("mitm_required"));
|
||||
}
|
||||
|
||||
ctx.insert(ProxyTarget(authority));
|
||||
ctx.insert(mode);
|
||||
req.extensions_mut().insert(ProxyTarget(authority));
|
||||
req.extensions_mut().insert(mode);
|
||||
if let Some(mitm_state) = mitm_state {
|
||||
ctx.insert(mitm_state);
|
||||
req.extensions_mut().insert(mitm_state);
|
||||
}
|
||||
|
||||
Ok((
|
||||
@@ -171,54 +192,59 @@ where
|
||||
.status(StatusCode::OK)
|
||||
.body(Body::empty())
|
||||
.unwrap_or_else(|_| Response::new(Body::empty())),
|
||||
ctx,
|
||||
req,
|
||||
))
|
||||
}
|
||||
|
||||
async fn http_connect_proxy<S>(ctx: Context<S>, upgraded: Upgraded) -> Result<(), Infallible>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let mode = ctx
|
||||
async fn http_connect_proxy(upgraded: Upgraded) -> Result<(), Infallible> {
|
||||
let mode = upgraded
|
||||
.extensions()
|
||||
.get::<NetworkMode>()
|
||||
.copied()
|
||||
.unwrap_or(NetworkMode::Full);
|
||||
|
||||
let Some(target) = ctx.get::<ProxyTarget>().map(|t| t.0.clone()) else {
|
||||
let Some(target) = upgraded
|
||||
.extensions()
|
||||
.get::<ProxyTarget>()
|
||||
.map(|t| t.0.clone())
|
||||
else {
|
||||
warn!("CONNECT missing proxy target");
|
||||
return Ok(());
|
||||
};
|
||||
let host = normalize_host(&target.host().to_string());
|
||||
let host = normalize_host(&target.host.to_string());
|
||||
|
||||
if ctx.get::<Arc<mitm::MitmState>>().is_some() {
|
||||
let port = target.port();
|
||||
if upgraded
|
||||
.extensions()
|
||||
.get::<Arc<mitm::MitmState>>()
|
||||
.is_some()
|
||||
{
|
||||
let port = target.port;
|
||||
info!("CONNECT MITM enabled (host={host}, port={port}, mode={mode:?})");
|
||||
if let Err(err) = mitm::mitm_tunnel(ctx, upgraded).await {
|
||||
if let Err(err) = mitm::mitm_tunnel(upgraded).await {
|
||||
warn!("MITM tunnel error: {err}");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let forwarder = Forwarder::ctx();
|
||||
if let Err(err) = forwarder.serve(ctx, upgraded).await {
|
||||
if let Err(err) = forwarder.serve(upgraded).await {
|
||||
warn!("tunnel error: {err}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn http_plain_proxy<S>(mut ctx: Context<S>, req: Request) -> Result<Response, Infallible>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let app_state = match ctx.get::<Arc<AppState>>().cloned() {
|
||||
async fn http_plain_proxy(
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
req: Request,
|
||||
) -> Result<Response, Infallible> {
|
||||
let app_state = match req.extensions().get::<Arc<AppState>>().cloned() {
|
||||
Some(state) => state,
|
||||
None => {
|
||||
error!("missing app state");
|
||||
return Ok(text_response(StatusCode::INTERNAL_SERVER_ERROR, "error"));
|
||||
}
|
||||
};
|
||||
let client = client_addr(&ctx);
|
||||
let client = client_addr(&req);
|
||||
|
||||
let method_allowed = match app_state.method_allowed(req.method().as_str()).await {
|
||||
Ok(allowed) => allowed,
|
||||
@@ -263,7 +289,7 @@ where
|
||||
Ok(true) => {
|
||||
let client = client.as_deref().unwrap_or_default();
|
||||
info!("unix socket allowed (client={client}, path={socket_path})");
|
||||
match proxy_via_unix_socket(ctx, req, &socket_path).await {
|
||||
match proxy_via_unix_socket(req, &socket_path).await {
|
||||
Ok(resp) => return Ok(resp),
|
||||
Err(err) => {
|
||||
warn!("unix socket proxy failed: {err}");
|
||||
@@ -286,21 +312,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let authority = match ctx
|
||||
.get_or_try_insert_with_ctx::<RequestContext, _>(|ctx| (ctx, &req).try_into())
|
||||
.map(|ctx| ctx.authority.clone())
|
||||
{
|
||||
let authority = match RequestContext::try_from(&req).map(|ctx| ctx.host_with_port()) {
|
||||
Ok(authority) => authority,
|
||||
Err(err) => {
|
||||
warn!("missing host: {err}");
|
||||
return Ok(text_response(StatusCode::BAD_REQUEST, "missing host"));
|
||||
}
|
||||
};
|
||||
let host = normalize_host(&authority.host().to_string());
|
||||
let port = authority.port();
|
||||
let host = normalize_host(&authority.host.to_string());
|
||||
let port = authority.port;
|
||||
|
||||
match app_state.host_blocked(&host, port).await {
|
||||
Ok((true, reason)) => {
|
||||
let request = NetworkPolicyRequest::new(
|
||||
NetworkProtocol::Http,
|
||||
host.clone(),
|
||||
port,
|
||||
client.clone(),
|
||||
Some(req.method().as_str().to_string()),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
match evaluate_host_policy(&app_state, policy_decider.as_ref(), &request).await {
|
||||
Ok(NetworkDecision::Deny { reason }) => {
|
||||
let _ = app_state
|
||||
.record_blocked(BlockedRequest::new(
|
||||
host.clone(),
|
||||
@@ -315,7 +348,7 @@ where
|
||||
warn!("request blocked (client={client}, host={host}, reason={reason})");
|
||||
return Ok(json_blocked(&host, &reason));
|
||||
}
|
||||
Ok((false, _)) => {}
|
||||
Ok(NetworkDecision::Allow) => {}
|
||||
Err(err) => {
|
||||
error!("failed to evaluate host for {host}: {err}");
|
||||
return Ok(text_response(StatusCode::INTERNAL_SERVER_ERROR, "error"));
|
||||
@@ -346,7 +379,7 @@ where
|
||||
info!("request allowed (client={client}, host={host}, method={method})");
|
||||
|
||||
let client = EasyHttpWebClient::default();
|
||||
match client.serve(ctx, req).await {
|
||||
match client.serve(req).await {
|
||||
Ok(resp) => Ok(resp),
|
||||
Err(err) => {
|
||||
warn!("upstream request failed: {err}");
|
||||
@@ -355,30 +388,24 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
async fn proxy_via_unix_socket<S>(
|
||||
ctx: Context<S>,
|
||||
req: Request,
|
||||
socket_path: &str,
|
||||
) -> Result<Response>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
async fn proxy_via_unix_socket(req: Request, socket_path: &str) -> Result<Response> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use rama::unix::client::UnixConnector;
|
||||
|
||||
let client = EasyHttpWebClient::builder()
|
||||
let client = EasyHttpWebClient::connector_builder()
|
||||
.with_custom_transport_connector(UnixConnector::fixed(socket_path))
|
||||
.without_tls_proxy_support()
|
||||
.without_proxy_support()
|
||||
.without_tls_support()
|
||||
.build();
|
||||
.with_default_http_connector()
|
||||
.build_client();
|
||||
|
||||
let (mut parts, body) = req.into_parts();
|
||||
let path = parts
|
||||
.uri
|
||||
.path_and_query()
|
||||
.map(rama::http::dep::http::uri::PathAndQuery::as_str)
|
||||
.map(rama::http::uri::PathAndQuery::as_str)
|
||||
.unwrap_or("/");
|
||||
parts.uri = path
|
||||
.parse()
|
||||
@@ -386,19 +413,20 @@ where
|
||||
parts.headers.remove("x-unix-socket");
|
||||
|
||||
let req = Request::from_parts(parts, body);
|
||||
Ok(client.serve(ctx, req).await?)
|
||||
Ok(client.serve(req).await?)
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
let _ = req;
|
||||
let _ = ctx;
|
||||
let _ = socket_path;
|
||||
Err(anyhow::anyhow!("unix sockets not supported"))
|
||||
}
|
||||
}
|
||||
|
||||
fn client_addr<S>(ctx: &Context<S>) -> Option<String> {
|
||||
ctx.get::<SocketInfo>()
|
||||
fn client_addr<T: ExtensionsRef>(input: &T) -> Option<String> {
|
||||
input
|
||||
.extensions()
|
||||
.get::<SocketInfo>()
|
||||
.map(|info| info.peer_addr().to_string())
|
||||
}
|
||||
|
||||
|
||||
@@ -3,55 +3,31 @@ mod config;
|
||||
mod http_proxy;
|
||||
mod init;
|
||||
mod mitm;
|
||||
mod network_policy;
|
||||
mod policy;
|
||||
mod proxy;
|
||||
mod responses;
|
||||
mod socks5;
|
||||
mod state;
|
||||
|
||||
use crate::state::AppState;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use clap::Subcommand;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tracing::warn;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[command(name = "codex-network-proxy", about = "Codex network sandbox proxy")]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Subcommand)]
|
||||
pub enum Command {
|
||||
/// Initialize the Codex network proxy directories (e.g. MITM cert paths).
|
||||
Init,
|
||||
}
|
||||
pub use network_policy::NetworkDecision;
|
||||
pub use network_policy::NetworkPolicyDecider;
|
||||
pub use network_policy::NetworkPolicyRequest;
|
||||
pub use network_policy::NetworkProtocol;
|
||||
pub use proxy::Args;
|
||||
pub use proxy::Command;
|
||||
pub use proxy::NetworkProxy;
|
||||
pub use proxy::NetworkProxyBuilder;
|
||||
pub use proxy::NetworkProxyHandle;
|
||||
pub use proxy::run_init;
|
||||
|
||||
pub async fn run_main(args: Args) -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
if let Some(Command::Init) = args.command {
|
||||
init::run_init()?;
|
||||
run_init()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if cfg!(not(target_os = "macos")) {
|
||||
warn!("allowUnixSockets is macOS-only; requests will be rejected on this platform");
|
||||
}
|
||||
|
||||
let state = Arc::new(AppState::new().await?);
|
||||
let runtime = config::resolve_runtime(&state.current_cfg().await?);
|
||||
|
||||
let http_addr: SocketAddr = runtime.http_addr;
|
||||
let socks_addr: SocketAddr = runtime.socks_addr;
|
||||
let admin_addr: SocketAddr = runtime.admin_addr;
|
||||
|
||||
let http_task = http_proxy::run_http_proxy(state.clone(), http_addr);
|
||||
let socks_task = socks5::run_socks5(state.clone(), socks_addr);
|
||||
let admin_task = admin::run_admin_api(state.clone(), admin_addr);
|
||||
|
||||
tokio::try_join!(http_task, socks_task, admin_task)?;
|
||||
Ok(())
|
||||
let proxy = NetworkProxy::from_cli_args(args).await?;
|
||||
proxy.run().await?.wait().await
|
||||
}
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use codex_network_proxy::Args;
|
||||
use codex_network_proxy::Command;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::run_init;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
codex_network_proxy::run_main(Args::parse()).await
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let args = Args::parse();
|
||||
if let Some(Command::Init) = args.command {
|
||||
run_init()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let proxy = NetworkProxy::from_cli_args(args).await?;
|
||||
proxy.run().await?.wait().await
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ use crate::state::BlockedRequest;
|
||||
use anyhow::Context as _;
|
||||
use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
use rama::Context;
|
||||
use rama::Layer;
|
||||
use rama::Service;
|
||||
use rama::bytes::Bytes;
|
||||
use rama::error::BoxError;
|
||||
use rama::error::OpaqueError;
|
||||
use rama::extensions::ExtensionsRef;
|
||||
use rama::futures::stream::Stream;
|
||||
use rama::http::Body;
|
||||
use rama::http::HeaderValue;
|
||||
@@ -29,6 +29,7 @@ use rama::http::layer::upgrade::Upgraded;
|
||||
use rama::http::server::HttpServer;
|
||||
use rama::net::proxy::ProxyTarget;
|
||||
use rama::net::stream::SocketInfo;
|
||||
use rama::rt::Executor;
|
||||
use rama::service::service_fn;
|
||||
use rama::tls::rustls::dep::pki_types::CertificateDer;
|
||||
use rama::tls::rustls::dep::pki_types::PrivateKeyDer;
|
||||
@@ -63,7 +64,7 @@ use rcgen_rama::SanType;
|
||||
|
||||
pub struct MitmState {
|
||||
issuer: Issuer<'static, KeyPair>,
|
||||
upstream: rama::service::BoxService<(), Request, Response, OpaqueError>,
|
||||
upstream: rama::service::BoxService<Request, Response, OpaqueError>,
|
||||
inspect: bool,
|
||||
max_body_bytes: usize,
|
||||
}
|
||||
@@ -88,16 +89,17 @@ impl MitmState {
|
||||
let issuer: Issuer<'static, KeyPair> =
|
||||
Issuer::from_ca_cert_pem(&ca_cert_pem, ca_key).context("failed to parse CA cert")?;
|
||||
|
||||
let tls_config = rama::tls::rustls::client::TlsConnectorData::new_http_auto()
|
||||
let tls_config = rama::tls::rustls::client::TlsConnectorData::try_new_http_auto()
|
||||
.context("create upstream TLS config")?;
|
||||
let upstream: rama::service::BoxService<(), Request, Response, OpaqueError> =
|
||||
EasyHttpWebClient::builder()
|
||||
let upstream: rama::service::BoxService<Request, Response, OpaqueError> =
|
||||
EasyHttpWebClient::connector_builder()
|
||||
// Use a direct transport connector (no upstream proxy) to avoid proxy loops.
|
||||
.with_default_transport_connector()
|
||||
.without_tls_proxy_support()
|
||||
.without_proxy_support()
|
||||
.with_tls_support_using_rustls(Some(tls_config))
|
||||
.build()
|
||||
.with_default_http_connector()
|
||||
.build_client()
|
||||
.boxed();
|
||||
|
||||
Ok(Self {
|
||||
@@ -135,23 +137,26 @@ impl MitmState {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mitm_tunnel<S>(ctx: Context<S>, upgraded: Upgraded) -> Result<()>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let state = ctx
|
||||
pub async fn mitm_tunnel(upgraded: Upgraded) -> Result<()> {
|
||||
let state = upgraded
|
||||
.extensions()
|
||||
.get::<Arc<MitmState>>()
|
||||
.cloned()
|
||||
.context("missing MITM state")?;
|
||||
let target = ctx
|
||||
let target = upgraded
|
||||
.extensions()
|
||||
.get::<ProxyTarget>()
|
||||
.context("missing proxy target")?
|
||||
.0
|
||||
.clone();
|
||||
let host = normalize_host(&target.host().to_string());
|
||||
let host = normalize_host(&target.host.to_string());
|
||||
let acceptor_data = state.tls_acceptor_data_for_host(&host)?;
|
||||
|
||||
let executor = ctx.executor().clone();
|
||||
let executor = upgraded
|
||||
.extensions()
|
||||
.get::<Executor>()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let http_service = HttpServer::auto(executor).service(
|
||||
(
|
||||
@@ -166,20 +171,14 @@ where
|
||||
.into_layer(http_service);
|
||||
|
||||
https_service
|
||||
.serve(ctx, upgraded)
|
||||
.serve(upgraded)
|
||||
.await
|
||||
.map_err(|err| anyhow!("MITM serve error: {err}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_mitm_request<S>(
|
||||
ctx: Context<S>,
|
||||
req: Request,
|
||||
) -> Result<Response, std::convert::Infallible>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let response = match forward_request(ctx, req).await {
|
||||
async fn handle_mitm_request(req: Request) -> Result<Response, std::convert::Infallible> {
|
||||
let response = match forward_request(req).await {
|
||||
Ok(resp) => resp,
|
||||
Err(err) => {
|
||||
warn!("MITM upstream request failed: {err}");
|
||||
@@ -189,27 +188,28 @@ where
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn forward_request<S>(ctx: Context<S>, req: Request) -> Result<Response>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let target = ctx
|
||||
async fn forward_request(req: Request) -> Result<Response> {
|
||||
let target = req
|
||||
.extensions()
|
||||
.get::<ProxyTarget>()
|
||||
.context("missing proxy target")?
|
||||
.0
|
||||
.clone();
|
||||
|
||||
let target_host = normalize_host(&target.host().to_string());
|
||||
let target_port = target.port();
|
||||
let mode = ctx
|
||||
let target_host = normalize_host(&target.host.to_string());
|
||||
let target_port = target.port;
|
||||
let mode = req
|
||||
.extensions()
|
||||
.get::<NetworkMode>()
|
||||
.copied()
|
||||
.unwrap_or(NetworkMode::Full);
|
||||
let mitm = ctx
|
||||
let mitm = req
|
||||
.extensions()
|
||||
.get::<Arc<MitmState>>()
|
||||
.cloned()
|
||||
.context("missing MITM state")?;
|
||||
let app_state = ctx
|
||||
let app_state = req
|
||||
.extensions()
|
||||
.get::<Arc<AppState>>()
|
||||
.cloned()
|
||||
.context("missing app state")?;
|
||||
@@ -223,7 +223,8 @@ where
|
||||
|
||||
let method = req.method().as_str().to_string();
|
||||
let path = path_and_query(req.uri());
|
||||
let client = ctx
|
||||
let client = req
|
||||
.extensions()
|
||||
.get::<SocketInfo>()
|
||||
.map(|info| info.peer_addr().to_string());
|
||||
|
||||
@@ -276,10 +277,7 @@ where
|
||||
};
|
||||
|
||||
let upstream_req = Request::from_parts(parts, body);
|
||||
let upstream_resp = mitm
|
||||
.upstream
|
||||
.serve(ctx.map_state(|_| ()), upstream_req)
|
||||
.await?;
|
||||
let upstream_resp = mitm.upstream.serve(upstream_req).await?;
|
||||
respond_with_inspection(
|
||||
upstream_resp,
|
||||
inspect,
|
||||
@@ -428,7 +426,7 @@ fn build_https_uri(authority: &str, path: &str) -> Result<Uri> {
|
||||
|
||||
fn path_and_query(uri: &Uri) -> String {
|
||||
uri.path_and_query()
|
||||
.map(rama::http::dep::http::uri::PathAndQuery::as_str)
|
||||
.map(rama::http::uri::PathAndQuery::as_str)
|
||||
.unwrap_or("/")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
113
codex-rs/network-proxy/src/network_policy.rs
Normal file
113
codex-rs/network-proxy/src/network_policy.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use crate::state::AppState;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum NetworkProtocol {
|
||||
Http,
|
||||
HttpsConnect,
|
||||
Socks5Tcp,
|
||||
Socks5Udp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NetworkPolicyRequest {
|
||||
pub protocol: NetworkProtocol,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub client_addr: Option<String>,
|
||||
pub method: Option<String>,
|
||||
pub command: Option<String>,
|
||||
pub exec_policy_hint: Option<String>,
|
||||
}
|
||||
|
||||
impl NetworkPolicyRequest {
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
protocol: NetworkProtocol,
|
||||
host: String,
|
||||
port: u16,
|
||||
client_addr: Option<String>,
|
||||
method: Option<String>,
|
||||
command: Option<String>,
|
||||
exec_policy_hint: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol,
|
||||
host,
|
||||
port,
|
||||
client_addr,
|
||||
method,
|
||||
command,
|
||||
exec_policy_hint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum NetworkDecision {
|
||||
Allow,
|
||||
Deny { reason: String },
|
||||
}
|
||||
|
||||
impl NetworkDecision {
|
||||
#[must_use]
|
||||
pub fn deny(reason: impl Into<String>) -> Self {
|
||||
let reason = reason.into();
|
||||
let reason = if reason.is_empty() {
|
||||
"policy_denied".to_string()
|
||||
} else {
|
||||
reason
|
||||
};
|
||||
Self::Deny { reason }
|
||||
}
|
||||
}
|
||||
|
||||
/// Decide whether a network request should be allowed.
|
||||
///
|
||||
/// If `command` or `exec_policy_hint` is provided, callers can map exec-policy
|
||||
/// approvals to network access (e.g., allow all requests for commands matching
|
||||
/// approved prefixes like `curl *`).
|
||||
#[async_trait]
|
||||
pub trait NetworkPolicyDecider: Send + Sync + 'static {
|
||||
async fn decide(&self, req: NetworkPolicyRequest) -> NetworkDecision;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<D: NetworkPolicyDecider + ?Sized> NetworkPolicyDecider for Arc<D> {
|
||||
async fn decide(&self, req: NetworkPolicyRequest) -> NetworkDecision {
|
||||
(**self).decide(req).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<F, Fut> NetworkPolicyDecider for F
|
||||
where
|
||||
F: Fn(NetworkPolicyRequest) -> Fut + Send + Sync + 'static,
|
||||
Fut: Future<Output = NetworkDecision> + Send,
|
||||
{
|
||||
async fn decide(&self, req: NetworkPolicyRequest) -> NetworkDecision {
|
||||
(self)(req).await
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn evaluate_host_policy(
|
||||
state: &AppState,
|
||||
decider: Option<&Arc<dyn NetworkPolicyDecider>>,
|
||||
request: &NetworkPolicyRequest,
|
||||
) -> Result<NetworkDecision> {
|
||||
let (blocked, reason) = state.host_blocked(&request.host, request.port).await?;
|
||||
if !blocked {
|
||||
return Ok(NetworkDecision::Allow);
|
||||
}
|
||||
|
||||
if reason == "not_allowed"
|
||||
&& let Some(decider) = decider
|
||||
{
|
||||
return Ok(decider.decide(request.clone()).await);
|
||||
}
|
||||
|
||||
Ok(NetworkDecision::deny(reason))
|
||||
}
|
||||
181
codex-rs/network-proxy/src/proxy.rs
Normal file
181
codex-rs/network-proxy/src/proxy.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use crate::admin;
|
||||
use crate::config;
|
||||
use crate::http_proxy;
|
||||
use crate::init;
|
||||
use crate::network_policy::NetworkPolicyDecider;
|
||||
use crate::socks5;
|
||||
use crate::state::AppState;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use clap::Subcommand;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::warn;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[command(name = "codex-network-proxy", about = "Codex network sandbox proxy")]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
/// Enable SOCKS5 UDP associate support (default: disabled).
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub enable_socks5_udp: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Subcommand)]
|
||||
pub enum Command {
|
||||
/// Initialize the Codex network proxy directories (e.g. MITM cert paths).
|
||||
Init,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct NetworkProxyBuilder {
|
||||
state: Option<Arc<AppState>>,
|
||||
http_addr: Option<SocketAddr>,
|
||||
socks_addr: Option<SocketAddr>,
|
||||
admin_addr: Option<SocketAddr>,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
enable_socks5_udp: bool,
|
||||
}
|
||||
|
||||
impl NetworkProxyBuilder {
|
||||
#[must_use]
|
||||
pub fn state(mut self, state: Arc<AppState>) -> Self {
|
||||
self.state = Some(state);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn http_addr(mut self, addr: SocketAddr) -> Self {
|
||||
self.http_addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn socks_addr(mut self, addr: SocketAddr) -> Self {
|
||||
self.socks_addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn admin_addr(mut self, addr: SocketAddr) -> Self {
|
||||
self.admin_addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn policy_decider<D>(mut self, decider: D) -> Self
|
||||
where
|
||||
D: NetworkPolicyDecider,
|
||||
{
|
||||
self.policy_decider = Some(Arc::new(decider));
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn policy_decider_arc(mut self, decider: Arc<dyn NetworkPolicyDecider>) -> Self {
|
||||
self.policy_decider = Some(decider);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn enable_socks5_udp(mut self, enabled: bool) -> Self {
|
||||
self.enable_socks5_udp = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn build(self) -> Result<NetworkProxy> {
|
||||
let state = match self.state {
|
||||
Some(state) => state,
|
||||
None => Arc::new(AppState::new().await?),
|
||||
};
|
||||
let runtime = config::resolve_runtime(&state.current_cfg().await?);
|
||||
|
||||
Ok(NetworkProxy {
|
||||
state,
|
||||
http_addr: self.http_addr.unwrap_or(runtime.http_addr),
|
||||
socks_addr: self.socks_addr.unwrap_or(runtime.socks_addr),
|
||||
admin_addr: self.admin_addr.unwrap_or(runtime.admin_addr),
|
||||
policy_decider: self.policy_decider,
|
||||
enable_socks5_udp: self.enable_socks5_udp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NetworkProxy {
|
||||
state: Arc<AppState>,
|
||||
http_addr: SocketAddr,
|
||||
socks_addr: SocketAddr,
|
||||
admin_addr: SocketAddr,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
enable_socks5_udp: bool,
|
||||
}
|
||||
|
||||
impl NetworkProxy {
|
||||
#[must_use]
|
||||
pub fn builder() -> NetworkProxyBuilder {
|
||||
NetworkProxyBuilder::default()
|
||||
}
|
||||
|
||||
pub async fn from_cli_args(args: Args) -> Result<Self> {
|
||||
let mut builder = Self::builder();
|
||||
builder = builder.enable_socks5_udp(args.enable_socks5_udp);
|
||||
builder.build().await
|
||||
}
|
||||
|
||||
pub async fn run(&self) -> Result<NetworkProxyHandle> {
|
||||
if cfg!(not(target_os = "macos")) {
|
||||
warn!("allowUnixSockets is macOS-only; requests will be rejected on this platform");
|
||||
}
|
||||
|
||||
let http_task = tokio::spawn(http_proxy::run_http_proxy(
|
||||
self.state.clone(),
|
||||
self.http_addr,
|
||||
self.policy_decider.clone(),
|
||||
));
|
||||
let socks_task = tokio::spawn(socks5::run_socks5(
|
||||
self.state.clone(),
|
||||
self.socks_addr,
|
||||
self.policy_decider.clone(),
|
||||
self.enable_socks5_udp,
|
||||
));
|
||||
let admin_task = tokio::spawn(admin::run_admin_api(self.state.clone(), self.admin_addr));
|
||||
|
||||
Ok(NetworkProxyHandle {
|
||||
http_task,
|
||||
socks_task,
|
||||
admin_task,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetworkProxyHandle {
|
||||
http_task: JoinHandle<Result<()>>,
|
||||
socks_task: JoinHandle<Result<()>>,
|
||||
admin_task: JoinHandle<Result<()>>,
|
||||
}
|
||||
|
||||
impl NetworkProxyHandle {
|
||||
pub async fn wait(self) -> Result<()> {
|
||||
self.http_task.await??;
|
||||
self.socks_task.await??;
|
||||
self.admin_task.await??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn shutdown(self) -> Result<()> {
|
||||
self.http_task.abort();
|
||||
self.socks_task.abort();
|
||||
self.admin_task.abort();
|
||||
let _ = self.http_task.await;
|
||||
let _ = self.socks_task.await;
|
||||
let _ = self.admin_task.await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_init() -> Result<()> {
|
||||
init::run_init()
|
||||
}
|
||||
@@ -1,16 +1,24 @@
|
||||
use crate::config::NetworkMode;
|
||||
use crate::network_policy::NetworkDecision;
|
||||
use crate::network_policy::NetworkPolicyDecider;
|
||||
use crate::network_policy::NetworkPolicyRequest;
|
||||
use crate::network_policy::NetworkProtocol;
|
||||
use crate::network_policy::evaluate_host_policy;
|
||||
use crate::policy::normalize_host;
|
||||
use crate::state::AppState;
|
||||
use crate::state::BlockedRequest;
|
||||
use anyhow::Context as _;
|
||||
use anyhow::Result;
|
||||
use rama::Context;
|
||||
use rama::Layer;
|
||||
use rama::Service;
|
||||
use rama::layer::AddExtensionLayer;
|
||||
use rama::extensions::ExtensionsRef;
|
||||
use rama::layer::AddInputExtensionLayer;
|
||||
use rama::net::stream::SocketInfo;
|
||||
use rama::proxy::socks5::Socks5Acceptor;
|
||||
use rama::proxy::socks5::server::DefaultConnector;
|
||||
use rama::proxy::socks5::server::DefaultUdpRelay;
|
||||
use rama::proxy::socks5::server::udp::RelayRequest;
|
||||
use rama::proxy::socks5::server::udp::RelayResponse;
|
||||
use rama::service::service_fn;
|
||||
use rama::tcp::client::Request as TcpRequest;
|
||||
use rama::tcp::client::service::TcpConnector;
|
||||
@@ -22,7 +30,12 @@ use tracing::error;
|
||||
use tracing::info;
|
||||
use tracing::warn;
|
||||
|
||||
pub async fn run_socks5(state: Arc<AppState>, addr: SocketAddr) -> Result<()> {
|
||||
pub async fn run_socks5(
|
||||
state: Arc<AppState>,
|
||||
addr: SocketAddr,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
enable_socks5_udp: bool,
|
||||
) -> Result<()> {
|
||||
let listener = TcpListener::build()
|
||||
.bind(addr)
|
||||
.await
|
||||
@@ -44,80 +57,193 @@ pub async fn run_socks5(state: Arc<AppState>, addr: SocketAddr) -> Result<()> {
|
||||
}
|
||||
|
||||
let tcp_connector = TcpConnector::default();
|
||||
let policy_tcp_connector = service_fn(move |ctx: Context<()>, req: TcpRequest| {
|
||||
let tcp_connector = tcp_connector.clone();
|
||||
async move {
|
||||
let app_state = ctx
|
||||
.get::<Arc<AppState>>()
|
||||
.cloned()
|
||||
.ok_or_else(|| io::Error::other("missing state"))?;
|
||||
let policy_tcp_connector = service_fn({
|
||||
let policy_decider = policy_decider.clone();
|
||||
move |req: TcpRequest| {
|
||||
let tcp_connector = tcp_connector.clone();
|
||||
let policy_decider = policy_decider.clone();
|
||||
async move {
|
||||
let app_state = req
|
||||
.extensions()
|
||||
.get::<Arc<AppState>>()
|
||||
.cloned()
|
||||
.ok_or_else(|| io::Error::other("missing state"))?;
|
||||
|
||||
let host = normalize_host(&req.authority().host().to_string());
|
||||
let port = req.authority().port();
|
||||
let client = ctx
|
||||
.get::<SocketInfo>()
|
||||
.map(|info| info.peer_addr().to_string());
|
||||
let host = normalize_host(&req.authority.host.to_string());
|
||||
let port = req.authority.port;
|
||||
let client = req
|
||||
.extensions()
|
||||
.get::<SocketInfo>()
|
||||
.map(|info| info.peer_addr().to_string());
|
||||
match app_state.network_mode().await {
|
||||
Ok(NetworkMode::Limited) => {
|
||||
let _ = app_state
|
||||
.record_blocked(BlockedRequest::new(
|
||||
host.clone(),
|
||||
"method_not_allowed".to_string(),
|
||||
client.clone(),
|
||||
None,
|
||||
Some(NetworkMode::Limited),
|
||||
"socks5".to_string(),
|
||||
))
|
||||
.await;
|
||||
let client = client.as_deref().unwrap_or_default();
|
||||
warn!(
|
||||
"SOCKS blocked by method policy (client={client}, host={host}, mode=limited, allowed_methods=GET, HEAD, OPTIONS)"
|
||||
);
|
||||
return Err(
|
||||
io::Error::new(io::ErrorKind::PermissionDenied, "blocked").into()
|
||||
);
|
||||
}
|
||||
Ok(NetworkMode::Full) => {}
|
||||
Err(err) => {
|
||||
error!("failed to evaluate method policy: {err}");
|
||||
return Err(io::Error::other("proxy error").into());
|
||||
}
|
||||
}
|
||||
|
||||
match app_state.network_mode().await {
|
||||
Ok(NetworkMode::Limited) => {
|
||||
let _ = app_state
|
||||
.record_blocked(BlockedRequest::new(
|
||||
host.clone(),
|
||||
"method_not_allowed".to_string(),
|
||||
client.clone(),
|
||||
None,
|
||||
Some(NetworkMode::Limited),
|
||||
"socks5".to_string(),
|
||||
))
|
||||
.await;
|
||||
let client = client.as_deref().unwrap_or_default();
|
||||
warn!(
|
||||
"SOCKS blocked by method policy (client={client}, host={host}, mode=limited, allowed_methods=GET, HEAD, OPTIONS)"
|
||||
);
|
||||
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "blocked").into());
|
||||
}
|
||||
Ok(NetworkMode::Full) => {}
|
||||
Err(err) => {
|
||||
error!("failed to evaluate method policy: {err}");
|
||||
return Err(io::Error::other("proxy error").into());
|
||||
let request = NetworkPolicyRequest::new(
|
||||
NetworkProtocol::Socks5Tcp,
|
||||
host.clone(),
|
||||
port,
|
||||
client.clone(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
match evaluate_host_policy(&app_state, policy_decider.as_ref(), &request).await {
|
||||
Ok(NetworkDecision::Deny { reason }) => {
|
||||
let _ = app_state
|
||||
.record_blocked(BlockedRequest::new(
|
||||
host.clone(),
|
||||
reason.clone(),
|
||||
client.clone(),
|
||||
None,
|
||||
None,
|
||||
"socks5".to_string(),
|
||||
))
|
||||
.await;
|
||||
let client = client.as_deref().unwrap_or_default();
|
||||
warn!("SOCKS blocked (client={client}, host={host}, reason={reason})");
|
||||
return Err(
|
||||
io::Error::new(io::ErrorKind::PermissionDenied, "blocked").into()
|
||||
);
|
||||
}
|
||||
Ok(NetworkDecision::Allow) => {
|
||||
let client = client.as_deref().unwrap_or_default();
|
||||
info!("SOCKS allowed (client={client}, host={host}, port={port})");
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to evaluate host: {err}");
|
||||
return Err(io::Error::other("proxy error").into());
|
||||
}
|
||||
}
|
||||
|
||||
tcp_connector.serve(req).await
|
||||
}
|
||||
|
||||
match app_state.host_blocked(&host, port).await {
|
||||
Ok((true, reason)) => {
|
||||
let _ = app_state
|
||||
.record_blocked(BlockedRequest::new(
|
||||
host.clone(),
|
||||
reason.clone(),
|
||||
client.clone(),
|
||||
None,
|
||||
None,
|
||||
"socks5".to_string(),
|
||||
))
|
||||
.await;
|
||||
let client = client.as_deref().unwrap_or_default();
|
||||
warn!("SOCKS blocked (client={client}, host={host}, reason={reason})");
|
||||
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "blocked").into());
|
||||
}
|
||||
Ok((false, _)) => {
|
||||
let client = client.as_deref().unwrap_or_default();
|
||||
info!("SOCKS allowed (client={client}, host={host}, port={port})");
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to evaluate host: {err}");
|
||||
return Err(io::Error::other("proxy error").into());
|
||||
}
|
||||
}
|
||||
|
||||
tcp_connector.serve(ctx, req).await
|
||||
}
|
||||
});
|
||||
|
||||
let socks_connector = DefaultConnector::default().with_connector(policy_tcp_connector);
|
||||
let socks_acceptor = Socks5Acceptor::new().with_connector(socks_connector);
|
||||
let base = Socks5Acceptor::new().with_connector(socks_connector);
|
||||
|
||||
listener
|
||||
.serve(AddExtensionLayer::new(state).into_layer(socks_acceptor))
|
||||
.await;
|
||||
if enable_socks5_udp {
|
||||
let udp_state = state.clone();
|
||||
let udp_decider = policy_decider.clone();
|
||||
let udp_relay = DefaultUdpRelay::default().with_async_inspector(service_fn(
|
||||
move |request: RelayRequest| {
|
||||
let udp_state = udp_state.clone();
|
||||
let udp_decider = udp_decider.clone();
|
||||
async move {
|
||||
let RelayRequest {
|
||||
server_address,
|
||||
payload,
|
||||
extensions,
|
||||
..
|
||||
} = request;
|
||||
|
||||
let host = normalize_host(&server_address.ip_addr.to_string());
|
||||
let port = server_address.port;
|
||||
let client = extensions
|
||||
.get::<SocketInfo>()
|
||||
.map(|info| info.peer_addr().to_string());
|
||||
|
||||
match udp_state.network_mode().await {
|
||||
Ok(NetworkMode::Limited) => {
|
||||
let _ = udp_state
|
||||
.record_blocked(BlockedRequest::new(
|
||||
host.clone(),
|
||||
"method_not_allowed".to_string(),
|
||||
client.clone(),
|
||||
None,
|
||||
Some(NetworkMode::Limited),
|
||||
"socks5-udp".to_string(),
|
||||
))
|
||||
.await;
|
||||
return Ok(RelayResponse {
|
||||
maybe_payload: None,
|
||||
extensions,
|
||||
});
|
||||
}
|
||||
Ok(NetworkMode::Full) => {}
|
||||
Err(err) => {
|
||||
error!("failed to evaluate method policy: {err}");
|
||||
return Err(io::Error::other("proxy error"));
|
||||
}
|
||||
}
|
||||
|
||||
let request = NetworkPolicyRequest::new(
|
||||
NetworkProtocol::Socks5Udp,
|
||||
host.clone(),
|
||||
port,
|
||||
client.clone(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
match evaluate_host_policy(&udp_state, udp_decider.as_ref(), &request).await {
|
||||
Ok(NetworkDecision::Deny { reason }) => {
|
||||
let _ = udp_state
|
||||
.record_blocked(BlockedRequest::new(
|
||||
host.clone(),
|
||||
reason.clone(),
|
||||
client.clone(),
|
||||
None,
|
||||
None,
|
||||
"socks5-udp".to_string(),
|
||||
))
|
||||
.await;
|
||||
let client = client.as_deref().unwrap_or_default();
|
||||
warn!(
|
||||
"SOCKS UDP blocked (client={client}, host={host}, reason={reason})"
|
||||
);
|
||||
Ok(RelayResponse {
|
||||
maybe_payload: None,
|
||||
extensions,
|
||||
})
|
||||
}
|
||||
Ok(NetworkDecision::Allow) => Ok(RelayResponse {
|
||||
maybe_payload: Some(payload),
|
||||
extensions,
|
||||
}),
|
||||
Err(err) => {
|
||||
error!("failed to evaluate UDP host: {err}");
|
||||
Err(io::Error::other("proxy error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
));
|
||||
let socks_acceptor = base.with_udp_associator(udp_relay);
|
||||
listener
|
||||
.serve(AddInputExtensionLayer::new(state).into_layer(socks_acceptor))
|
||||
.await;
|
||||
} else {
|
||||
listener
|
||||
.serve(AddInputExtensionLayer::new(state).into_layer(base))
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user