├── .conan ├── profiles │ ├── windows │ ├── macos │ ├── freebsd │ ├── ios │ ├── linux │ └── boost ├── scripts │ ├── latest.lock │ ├── conan.sh │ ├── export.sh │ └── path.sh └── recipes │ ├── libmaxminddb │ ├── conandata.yml │ └── conanfile.py │ └── boringssl │ ├── conandata.yml │ └── conanfile.py ├── schemas └── examples │ ├── egress │ ├── direct.json │ ├── reject.json │ ├── socks5.json │ ├── reject-drop.json │ ├── http.json │ ├── https.json │ ├── socks5s.json │ ├── ss.json │ ├── vmess.json │ ├── trojan.json │ ├── https-with-auth.json │ ├── socks5-with-auth.json │ ├── trojan-with-ws.json │ └── vmess-with-tls-ws.json │ └── ingress │ ├── transparent.json │ ├── http.json │ ├── socks5.json │ ├── ss.json │ ├── tunnel.json │ ├── vmess.json │ ├── https.json │ ├── socks5s.json │ ├── trojan.json │ ├── vmess-with-tls-ws.json │ ├── https-with-auth.json │ ├── socks5s-with-auth.json │ └── trojan-with-ws.json ├── images ├── tip.png ├── overview.png ├── use_case_0.png ├── use_case_1.png ├── use_case_2.png └── use_case_3.png ├── test ├── geo.mmdb ├── utils.cpp ├── utils.hpp ├── CMakeLists.txt ├── vo.hpp ├── base64.cpp ├── keys.cpp ├── balancer.cpp └── method.cpp ├── cmake ├── test │ ├── sodium-type.c │ ├── P0702R1.cpp │ ├── boost-test-type.cpp │ ├── maxminddb-version.cpp │ └── brotli-version.cpp ├── AddFoundTarget.cmake ├── FindBoringSSL.cmake ├── GetLibraryDirectories.cmake ├── FindBrotli.cmake ├── FindMaxmindDB.cmake ├── Findlibsodium.cmake ├── FindMbedTLS.cmake ├── FindRapidJSON.cmake ├── Configure.cmake └── HandleDependencies.cmake ├── .gitattributes ├── server ├── pichi.json.default ├── CMakeLists.txt └── main.cpp ├── .github └── workflows │ ├── macos.yml │ ├── linux.yml │ ├── windows.yml │ ├── ios.yml │ ├── android.yml │ ├── conan.yml │ └── docker.yml ├── include ├── pichi │ ├── common │ │ ├── constants.hpp │ │ ├── uri.hpp │ │ ├── error.hpp │ │ ├── asserts.hpp │ │ ├── literals.hpp │ │ ├── adapter.hpp │ │ ├── endpoint.hpp │ │ ├── enumerations.hpp │ │ ├── config.hpp.in │ │ └── buffer.hpp │ ├── crypto │ │ ├── key.hpp │ │ ├── fingerprint.hpp │ │ ├── base64.hpp │ │ ├── brotli.hpp │ │ ├── aead.hpp │ │ └── stream.hpp │ ├── vo │ │ ├── error.hpp │ │ ├── route.hpp │ │ ├── rule.hpp │ │ ├── egress.hpp │ │ ├── ingress.hpp │ │ ├── parse.hpp │ │ ├── credential.hpp │ │ ├── to_json.hpp │ │ ├── options.hpp │ │ ├── iterator.hpp │ │ └── messages.hpp │ ├── stream │ │ ├── traits.hpp │ │ ├── tls.hpp │ │ └── test.hpp │ ├── net │ │ ├── adapter.hpp │ │ ├── direct.hpp │ │ ├── reject.hpp │ │ ├── transparent.hpp │ │ ├── spawn.hpp │ │ ├── ssstream.hpp │ │ ├── tunnel.hpp │ │ ├── ssaead.hpp │ │ ├── socks5.hpp │ │ ├── trojan.hpp │ │ └── http.hpp │ └── api │ │ ├── egress_manager.hpp │ │ ├── ingress_holder.hpp │ │ ├── rest.hpp │ │ ├── session.hpp │ │ ├── ingress_manager.hpp │ │ ├── balancer.hpp │ │ ├── server.hpp │ │ └── router.hpp └── pichi.h ├── .gitignore ├── src ├── vo │ ├── error.cpp │ ├── route.cpp │ └── rule.cpp ├── pichi.cpp ├── net │ ├── direct.cpp │ ├── spawn.cpp │ ├── reject.cpp │ └── tunnel.cpp ├── api │ ├── egress_manager.cpp │ ├── ingress_holder.cpp │ ├── ingress_manager.cpp │ ├── session.cpp │ └── balancer.cpp ├── common │ ├── asserts.cpp │ ├── uri.cpp │ └── error.cpp ├── crypto │ ├── brotli.cpp │ ├── fingerprint.cpp │ ├── key.cpp │ ├── base64.cpp │ └── hash.cpp └── CMakeLists.txt ├── docker └── pichi.dockerfile ├── LICENSE ├── CMakeLists.txt └── conanfile.py /.conan/profiles/windows: -------------------------------------------------------------------------------- 1 | include(default) 2 | include(boost) 3 | -------------------------------------------------------------------------------- /schemas/examples/egress/direct.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "direct" 3 | } -------------------------------------------------------------------------------- /schemas/examples/egress/reject.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "reject" 3 | } -------------------------------------------------------------------------------- /images/tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichi-router/pichi/HEAD/images/tip.png -------------------------------------------------------------------------------- /test/geo.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichi-router/pichi/HEAD/test/geo.mmdb -------------------------------------------------------------------------------- /images/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichi-router/pichi/HEAD/images/overview.png -------------------------------------------------------------------------------- /cmake/test/sodium-type.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void) { return sodium_init(); } 4 | -------------------------------------------------------------------------------- /images/use_case_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichi-router/pichi/HEAD/images/use_case_0.png -------------------------------------------------------------------------------- /images/use_case_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichi-router/pichi/HEAD/images/use_case_1.png -------------------------------------------------------------------------------- /images/use_case_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichi-router/pichi/HEAD/images/use_case_2.png -------------------------------------------------------------------------------- /images/use_case_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichi-router/pichi/HEAD/images/use_case_3.png -------------------------------------------------------------------------------- /.conan/profiles/macos: -------------------------------------------------------------------------------- 1 | include(default) 2 | include(boost) 3 | 4 | [options] 5 | pichi/*:transparent=pf 6 | -------------------------------------------------------------------------------- /.conan/profiles/freebsd: -------------------------------------------------------------------------------- 1 | include(default) 2 | include(boost) 3 | 4 | [options] 5 | b2/*:toolset=clang 6 | pichi/*:transparent=pf 7 | -------------------------------------------------------------------------------- /schemas/examples/egress/socks5.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "socks5", 3 | "server": { 4 | "host": "::1", 5 | "port": 3128 6 | } 7 | } -------------------------------------------------------------------------------- /schemas/examples/egress/reject-drop.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "reject", 3 | "reject": { 4 | "mode": "fixed", 5 | "delay": 300 6 | } 7 | } -------------------------------------------------------------------------------- /schemas/examples/egress/http.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "http", 3 | "server": { 4 | "host": "proxy.example.com", 5 | "port": 3128 6 | } 7 | } -------------------------------------------------------------------------------- /cmake/test/P0702R1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char* argv[]) 4 | { 5 | auto a = std::array{0, 1}; 6 | return a[0]; 7 | } 8 | -------------------------------------------------------------------------------- /.conan/profiles/ios: -------------------------------------------------------------------------------- 1 | include(default) 2 | include(boost) 3 | 4 | [settings] 5 | 6 | [options] 7 | pichi/*:build_test=False 8 | pichi/*:build_server=False 9 | -------------------------------------------------------------------------------- /.conan/profiles/linux: -------------------------------------------------------------------------------- 1 | include(default) 2 | include(boost) 3 | 4 | [settings] 5 | compiler.libcxx=libstdc++11 6 | 7 | [options] 8 | pichi/*:transparent=iptables 9 | -------------------------------------------------------------------------------- /schemas/examples/ingress/transparent.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "transparent", 3 | "bind": [ 4 | { 5 | "host": "::", 6 | "port": 1001 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.cmake text 4 | *.cpp text 5 | *.h.in text 6 | *.h text 7 | *.hpp text 8 | *.sh text eol=lf 9 | *.mmdb binary 10 | *.png binary 11 | -------------------------------------------------------------------------------- /cmake/test/boost-test-type.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE Check Boost_Test Type 2 | #include 3 | 4 | BOOST_AUTO_TEST_SUITE(Check) 5 | BOOST_AUTO_TEST_SUITE_END() 6 | -------------------------------------------------------------------------------- /schemas/examples/egress/https.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "http", 3 | "server": { 4 | "host": "::1", 5 | "port": 3128 6 | }, 7 | "tls": { 8 | "insecure": true 9 | } 10 | } -------------------------------------------------------------------------------- /cmake/test/maxminddb-version.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int, char**) 5 | { 6 | std::cout << MMDB_lib_version() << std::endl; 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /schemas/examples/egress/socks5s.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "socks5", 3 | "server": { 4 | "host": "::1", 5 | "port": 3128 6 | }, 7 | "tls": { 8 | "insecure": true 9 | } 10 | } -------------------------------------------------------------------------------- /schemas/examples/ingress/http.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "http", 3 | "bind": [ 4 | { 5 | "host": "::1", 6 | "port": 3128 7 | }, 8 | { 9 | "host": "127.0.0.1", 10 | "port": 3128 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /schemas/examples/ingress/socks5.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "socks5", 3 | "bind": [ 4 | { 5 | "host": "::1", 6 | "port": 1080 7 | }, 8 | { 9 | "host": "127.0.0.1", 10 | "port": 1080 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /server/pichi.json.default: -------------------------------------------------------------------------------- 1 | { 2 | "ingresses": {}, 3 | "egresses": { 4 | "direct": { 5 | "type": "direct" 6 | } 7 | }, 8 | "rules": {}, 9 | "route": { 10 | "default": "direct", 11 | "rules": [] 12 | } 13 | } -------------------------------------------------------------------------------- /schemas/examples/egress/ss.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ss", 3 | "server": { 4 | "host": "ss.example.com", 5 | "port": 8388 6 | }, 7 | "option": { 8 | "password": "shadowsocks password", 9 | "method": "rc4-md5" 10 | } 11 | } -------------------------------------------------------------------------------- /schemas/examples/egress/vmess.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "vmess", 3 | "server": { 4 | "host": "vmess.example.com", 5 | "port": 443 6 | }, 7 | "credential": { 8 | "uuid": "22d1d4eb-3619-48fd-ae74-bfef06ac5c3d", 9 | "alter_id": 64 10 | } 11 | } -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: macOS 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | 9 | jobs: 10 | macos: 11 | uses: ./.github/workflows/conan.yml 12 | with: 13 | image: macos-14 14 | os: macos 15 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | 9 | jobs: 10 | linux: 11 | uses: ./.github/workflows/conan.yml 12 | with: 13 | image: ubuntu-22.04 14 | os: linux 15 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | 9 | jobs: 10 | windows: 11 | uses: ./.github/workflows/conan.yml 12 | with: 13 | image: windows-2022 14 | os: windows 15 | -------------------------------------------------------------------------------- /cmake/test/brotli-version.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int, char**) 5 | { 6 | auto version = BrotliDecoderVersion(); 7 | std::cout << (version >> 24) << "." << ((version >> 12) & 0xfff) << "." << (version & 0xfff); 8 | return 0; 9 | } -------------------------------------------------------------------------------- /schemas/examples/egress/trojan.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "trojan", 3 | "server": { 4 | "host": "trojan.example.com", 5 | "port": 443 6 | }, 7 | "credential": { 8 | "password": "trojan password" 9 | }, 10 | "tls": { 11 | "insecure": true 12 | } 13 | } -------------------------------------------------------------------------------- /.github/workflows/ios.yml: -------------------------------------------------------------------------------- 1 | name: iOS 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | 9 | jobs: 10 | macos: 11 | uses: ./.github/workflows/conan.yml 12 | with: 13 | image: macos-14 14 | os: ios 15 | args: -a armv8 -v 17.0 16 | -------------------------------------------------------------------------------- /include/pichi/common/constants.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_COMMON_CONSTANTS_HPP 2 | #define PICHI_COMMON_CONSTANTS_HPP 3 | 4 | #include 5 | 6 | namespace pichi { 7 | 8 | size_t const MAX_FRAME_SIZE = 0x3fff; 9 | 10 | } // namespace pichi 11 | 12 | #endif // PICHI_COMMON_CONSTANTS_HPP 13 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | 9 | jobs: 10 | android: 11 | uses: ./.github/workflows/conan.yml 12 | with: 13 | image: ubuntu-22.04 14 | os: android 15 | args: -r android-ndk/r27c -l 35 -a armv8 16 | -------------------------------------------------------------------------------- /.conan/scripts/latest.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.5", 3 | "requires": [ 4 | "boost/1.85.0", 5 | "boringssl/32", 6 | "brotli/1.1.0", 7 | "libmaxminddb/1.10.0", 8 | "libsodium/1.0.20", 9 | "mbedtls/3.6.2", 10 | "rapidjson/1.1.0", 11 | "openssl/3.3.2" 12 | ], 13 | "python_requires": [] 14 | } 15 | -------------------------------------------------------------------------------- /schemas/examples/ingress/ss.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ss", 3 | "bind": [ 4 | { 5 | "host": "::", 6 | "port": 8388 7 | }, 8 | { 9 | "host": "0.0.0.0", 10 | "port": 8388 11 | } 12 | ], 13 | "option": { 14 | "method": "xchacha20-ietf-poly1305", 15 | "password": "shadowsocks password" 16 | } 17 | } -------------------------------------------------------------------------------- /schemas/examples/ingress/tunnel.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "tunnel", 3 | "tunnel": { 4 | "destinations": [ 5 | { 6 | "host": "tunnel0.example.com", 7 | "port": 853 8 | }, 9 | { 10 | "host": "tunnel1.example.com", 11 | "port": 853 12 | } 13 | ], 14 | "balance": "random" 15 | } 16 | } -------------------------------------------------------------------------------- /cmake/AddFoundTarget.cmake: -------------------------------------------------------------------------------- 1 | macro(_add_found_target target inc lib) 2 | if(NOT TARGET ${target}) 3 | add_library(${target} UNKNOWN IMPORTED) 4 | set_target_properties(${target} PROPERTIES 5 | INTERFACE_INCLUDE_DIRECTORIES "${inc}" 6 | IMPORTED_LINK_INTERFACE_LANGUAGES "C" 7 | IMPORTED_LOCATION "${lib}") 8 | endif() 9 | endmacro() 10 | -------------------------------------------------------------------------------- /schemas/examples/ingress/vmess.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "vmess", 3 | "bind": [ 4 | { 5 | "host": "::", 6 | "port": 443 7 | } 8 | ], 9 | "credentials": [ 10 | { 11 | "uuid": "fa314a7b-a26b-42ee-8b94-6ac4ca5ae895" 12 | }, 13 | { 14 | "uuid": "67f05624-28b4-4470-b6dd-a5802cdb80dc", 15 | "alter_id": 64 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /schemas/examples/egress/https-with-auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "http", 3 | "server": { 4 | "host": "::1", 5 | "port": 3128 6 | }, 7 | "credential": { 8 | "username": "http_proxy_user_000", 9 | "password": "http_proxy_pass_000" 10 | }, 11 | "tls": { 12 | "insecure": false, 13 | "server_name": "example.com", 14 | "sni": "www.example.com" 15 | } 16 | } -------------------------------------------------------------------------------- /schemas/examples/ingress/https.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "http", 3 | "bind": [ 4 | { 5 | "host": "::1", 6 | "port": 3128 7 | }, 8 | { 9 | "host": "127.0.0.1", 10 | "port": 3128 11 | } 12 | ], 13 | "tls": { 14 | "key_file": "/etc/letsencrypt/live/example.com/privkey.pem", 15 | "cert_file": "/etc/letsencrypt/live/example.com/fullchain.pem" 16 | } 17 | } -------------------------------------------------------------------------------- /schemas/examples/ingress/socks5s.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "socks5", 3 | "bind": [ 4 | { 5 | "host": "::1", 6 | "port": 1080 7 | }, 8 | { 9 | "host": "127.0.0.1", 10 | "port": 1080 11 | } 12 | ], 13 | "tls": { 14 | "key_file": "/etc/letsencrypt/live/example.com/privkey.pem", 15 | "cert_file": "/etc/letsencrypt/live/example.com/fullchain.pem" 16 | } 17 | } -------------------------------------------------------------------------------- /include/pichi/crypto/key.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_CRYPTO_KEY_HPP 2 | #define PICHI_CRYPTO_KEY_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace pichi::crypto { 10 | 11 | extern size_t generateKey(CryptoMethod, ConstBuffer, MutableBuffer); 12 | 13 | } // namespace pichi::crypto 14 | 15 | #endif // PICHI_CRYPTO_KEY_HPP 16 | -------------------------------------------------------------------------------- /include/pichi/vo/error.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_VO_ERROR_HPP 2 | #define PICHI_VO_ERROR_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace pichi::vo { 8 | 9 | struct Error { 10 | std::string_view message_; 11 | }; 12 | 13 | extern rapidjson::Value toJson(Error const&, rapidjson::Document::AllocatorType&); 14 | 15 | } // namespace pichi::vo 16 | 17 | #endif // PICHI_VO_ERROR_HPP 18 | -------------------------------------------------------------------------------- /include/pichi/crypto/fingerprint.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_CRYPTO_FINGERPRINT_HPP 2 | #define PICHI_CRYPTO_FINGERPRINT_HPP 3 | 4 | #include 5 | 6 | namespace pichi::crypto { 7 | 8 | extern void enableBrotliCompression(::SSL_CTX*); 9 | extern void setupTlsFingerprint(::SSL_CTX*); 10 | extern void setupTlsFingerprint(::SSL*); 11 | 12 | } // namespace pichi::crypto 13 | 14 | #endif // PICHI_CRYPTO_FINGERPRINT_HPP 15 | -------------------------------------------------------------------------------- /schemas/examples/egress/socks5-with-auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "socks5", 3 | "server": { 4 | "host": "::1", 5 | "port": 3128 6 | }, 7 | "credential": { 8 | "username": "socks5_proxy_user_000", 9 | "password": "socks5_proxy_pass_000" 10 | }, 11 | "tls": { 12 | "insecure": false, 13 | "ca_file": "/etc/cert/rootCA.pem", 14 | "server_name": "example.com", 15 | "sni": "www.example.com" 16 | } 17 | } -------------------------------------------------------------------------------- /include/pichi/crypto/base64.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_CRYPTO_BASE64_HPP 2 | #define PICHI_CRYPTO_BASE64_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace pichi::crypto { 8 | 9 | extern std::string base64Encode(std::string_view); 10 | extern std::string base64Decode(std::string_view, PichiError = PichiError::MISC); 11 | 12 | } // namespace pichi::crypto 13 | 14 | #endif // PICHI_CRYPTO_BASE64_HPP 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | # Temporary files for editors 16 | .#* 17 | .*.swp 18 | 19 | # Editors 20 | .clang-format 21 | build/ 22 | .vscode/ 23 | .clang_complete 24 | 25 | # CMake 26 | CMakePresets.json 27 | CMakeUserPresets.json 28 | 29 | # Extensions 30 | .devcontainer/ 31 | -------------------------------------------------------------------------------- /schemas/examples/egress/trojan-with-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "trojan", 3 | "server": { 4 | "host": "trojan.example.com", 5 | "port": 443 6 | }, 7 | "credential": { 8 | "password": "trojan password" 9 | }, 10 | "tls": { 11 | "insecure": false, 12 | "ca_file": "/etc/cert/rootCA.pem", 13 | "server_name": "example.com", 14 | "sni": "www.example.com" 15 | }, 16 | "websocket": { 17 | "path": "/encryption" 18 | } 19 | } -------------------------------------------------------------------------------- /server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SERVER pichi) 2 | 3 | add_executable(${SERVER} main.cpp run.cpp) 4 | 5 | target_link_libraries(${SERVER} PRIVATE ${PICHI_LIBRARY} 6 | ${COMMON_LIBRARIES} Boost::program_options Boost::filesystem) 7 | set_target_properties(${SERVER} PROPERTIES 8 | INSTALL_RPATH ${INSTALL_RPATH} 9 | MSVC_RUNTIME_LIBRARY ${MSVC_CRT} 10 | INSTALL_RPATH_USE_LINK_PATH TRUE) 11 | install(TARGETS ${SERVER} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 12 | -------------------------------------------------------------------------------- /include/pichi/crypto/brotli.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_CRYPTO_FINGERPRINT_HPP 2 | #define PICHI_CRYPTO_FINGERPRINT_HPP 3 | 4 | #include 5 | 6 | namespace pichi::crypto { 7 | 8 | template int brotliCompress(SSL*, CBB*, uint8_t const*, size_t); 9 | template 10 | int brotliDecompress(SSL*, CRYPTO_BUFFER**, size_t, uint8_t const*, size_t); 11 | 12 | } // namespace pichi::crypto 13 | 14 | #endif // PICHI_CRYPTO_FINGERPRINT_HPP 15 | -------------------------------------------------------------------------------- /schemas/examples/egress/vmess-with-tls-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "vmess", 3 | "server": { 4 | "host": "vmess.example.com", 5 | "port": 443 6 | }, 7 | "credential": { 8 | "uuid": "22d1d4eb-3619-48fd-ae74-bfef06ac5c3d", 9 | "alter_id": 64, 10 | "security": "auto" 11 | }, 12 | "tls": { 13 | "insecure": false, 14 | "ca_file": "/etc/cert/rootCA.pem", 15 | "server_name": "example.com", 16 | "sni": "www.example.com" 17 | }, 18 | "websocket": { 19 | "path": "/encryption" 20 | } 21 | } -------------------------------------------------------------------------------- /include/pichi/stream/traits.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_STREAM_TRAITS_HPP 2 | #define PICHI_STREAM_TRAITS_HPP 3 | 4 | namespace pichi::stream { 5 | 6 | template struct AsyncStream : public std::true_type { 7 | }; 8 | 9 | template struct RawStream : public std::true_type { 10 | }; 11 | 12 | template inline constexpr bool IsRawStream = RawStream::value; 13 | template inline constexpr bool IsAsyncStream = AsyncStream::value; 14 | 15 | } // namespace pichi::stream 16 | 17 | #endif // PICHI_STREAM_TRAITS_HPP 18 | -------------------------------------------------------------------------------- /include/pichi.h: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_H 2 | #define PICHI_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif // __cplusplus 9 | 10 | /* 11 | * Start PICHI server according to 12 | * - bind: server listening address, NOT NULL, 13 | * - port: server listening port, 14 | * - mmdb: IP GEO database, MMDB format, NOT NULL. 15 | * The function doesn't return if no error occurs, otherwise -1. 16 | */ 17 | extern int pichi_run_server(char const* bind, uint16_t port, char const* mmdb); 18 | 19 | #ifdef __cplusplus 20 | } 21 | #endif // __cplusplus 22 | 23 | #endif // PICHI_H 24 | -------------------------------------------------------------------------------- /schemas/examples/ingress/trojan.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "trojan", 3 | "bind": [ 4 | { 5 | "host": "::", 6 | "port": 443 7 | }, 8 | { 9 | "host": "0.0.0.0", 10 | "port": 443 11 | } 12 | ], 13 | "trojan": { 14 | "remote": { 15 | "host": "fake.example.com", 16 | "port": 443 17 | } 18 | }, 19 | "credentials": [ 20 | { 21 | "password": "trojan password" 22 | } 23 | ], 24 | "tls": { 25 | "key_file": "/etc/letsencrypt/live/example.com/privkey.pem", 26 | "cert_file": "/etc/letsencrypt/live/example.com/fullchain.pem" 27 | } 28 | } -------------------------------------------------------------------------------- /include/pichi/vo/route.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_VO_ROUTE_HPP 2 | #define PICHI_VO_ROUTE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace pichi::vo { 11 | 12 | struct Route { 13 | std::optional default_ = {}; 14 | std::vector, std::string>> rules_ = {}; 15 | }; 16 | 17 | extern rapidjson::Value toJson(Route const&, rapidjson::Document::AllocatorType&); 18 | 19 | extern bool operator==(Route const&, Route const&); 20 | 21 | } // namespace pichi::vo 22 | 23 | #endif // PICHI_VO_ROUTE_HPP 24 | -------------------------------------------------------------------------------- /schemas/examples/ingress/vmess-with-tls-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "vmess", 3 | "bind": [ 4 | { 5 | "host": "::", 6 | "port": 443 7 | } 8 | ], 9 | "credentials": [ 10 | { 11 | "uuid": "fa314a7b-a26b-42ee-8b94-6ac4ca5ae895" 12 | }, 13 | { 14 | "uuid": "67f05624-28b4-4470-b6dd-a5802cdb80dc", 15 | "alter_id": 64 16 | } 17 | ], 18 | "tls": { 19 | "key_file": "/etc/letsencrypt/live/example.com/privkey.pem", 20 | "cert_file": "/etc/letsencrypt/live/example.com/fullchain.pem" 21 | }, 22 | "websocket": { 23 | "path": "/encryption", 24 | "host": "example.com" 25 | } 26 | } -------------------------------------------------------------------------------- /include/pichi/common/uri.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_COMMON_URI_HPP 2 | #define PICHI_COMMON_URI_HPP 3 | 4 | #include 5 | 6 | namespace pichi { 7 | 8 | struct Uri { 9 | Uri(std::string_view); 10 | 11 | std::string_view all_; 12 | std::string_view scheme_; 13 | std::string_view host_; 14 | std::string_view port_; 15 | std::string_view suffix_; 16 | std::string_view path_; 17 | std::string_view query_; 18 | }; 19 | 20 | struct HostAndPort { 21 | HostAndPort(std::string_view); 22 | 23 | std::string_view host_; 24 | std::string_view port_; 25 | }; 26 | 27 | } // namespace pichi 28 | 29 | #endif // PICHI_COMMON_URI_HPP 30 | -------------------------------------------------------------------------------- /schemas/examples/ingress/https-with-auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "http", 3 | "bind": [ 4 | { 5 | "host": "::1", 6 | "port": 3128 7 | }, 8 | { 9 | "host": "127.0.0.1", 10 | "port": 3128 11 | } 12 | ], 13 | "credentials": [ 14 | { 15 | "username": "http_proxy_user_000", 16 | "password": "http_proxy_pass_000" 17 | }, 18 | { 19 | "username": "http_proxy_user_001", 20 | "password": "http_proxy_pass_001" 21 | } 22 | ], 23 | "tls": { 24 | "key_file": "/etc/letsencrypt/live/example.com/privkey.pem", 25 | "cert_file": "/etc/letsencrypt/live/example.com/fullchain.pem" 26 | } 27 | } -------------------------------------------------------------------------------- /schemas/examples/ingress/socks5s-with-auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "socks5", 3 | "bind": [ 4 | { 5 | "host": "::1", 6 | "port": 1080 7 | }, 8 | { 9 | "host": "127.0.0.1", 10 | "port": 1080 11 | } 12 | ], 13 | "credentials": [ 14 | { 15 | "username": "socks5_proxy_user_000", 16 | "password": "socks5_proxy_pass_000" 17 | }, 18 | { 19 | "username": "socks5_proxy_user_001", 20 | "password": "socks5_proxy_pass_001" 21 | } 22 | ], 23 | "tls": { 24 | "key_file": "/etc/letsencrypt/live/example.com/privkey.pem", 25 | "cert_file": "/etc/letsencrypt/live/example.com/fullchain.pem" 26 | } 27 | } -------------------------------------------------------------------------------- /schemas/examples/ingress/trojan-with-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "trojan", 3 | "bind": [ 4 | { 5 | "host": "::", 6 | "port": 443 7 | }, 8 | { 9 | "host": "0.0.0.0", 10 | "port": 443 11 | } 12 | ], 13 | "trojan": { 14 | "remote": { 15 | "host": "fake.example.com", 16 | "port": 443 17 | } 18 | }, 19 | "credentials": [ 20 | { 21 | "password": "trojan password" 22 | } 23 | ], 24 | "tls": { 25 | "key_file": "/etc/letsencrypt/live/example.com/privkey.pem", 26 | "cert_file": "/etc/letsencrypt/live/example.com/fullchain.pem" 27 | }, 28 | "websocket": { 29 | "path": "/encryption", 30 | "host": "example.com" 31 | } 32 | } -------------------------------------------------------------------------------- /.conan/scripts/conan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | usage() 4 | { 5 | cat < [options] 8 | available sub commands are: 9 | build : Build pichi with conan 10 | export: Export recipe inside .conan/recipes 11 | path : Show the path to the conan cache for given options 12 | EOF 13 | exit 1 14 | } 15 | 16 | # Main 17 | if [ "$#" -lt 1 ]; then 18 | usage 19 | fi 20 | 21 | scripts="$(dirname ${0})" 22 | 23 | case "${1}" in 24 | build) 25 | shift 26 | "${scripts}/build.sh" "$@" 27 | ;; 28 | path) 29 | shift 30 | "${scripts}/path.sh" "$@" 31 | ;; 32 | export) 33 | shift 34 | "${scripts}/export.sh" "$@" 35 | ;; 36 | *) usage;; 37 | esac 38 | -------------------------------------------------------------------------------- /include/pichi/common/error.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_COMMON_ERROR_HPP 2 | #define PICHI_COMMON_ERROR_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace boost::system { 10 | 11 | template <> struct is_error_code_enum : public std::true_type { 12 | }; 13 | 14 | } // namespace boost::system 15 | 16 | namespace pichi { 17 | 18 | extern boost::system::error_category const& PICHI_CATEGORY; 19 | extern boost::system::error_code make_error_code(PichiError); 20 | extern boost::system::error_code makeErrorCode(PichiError); 21 | 22 | } // namespace pichi 23 | 24 | #endif // PICHI_COMMON_ERROR_HPP 25 | -------------------------------------------------------------------------------- /.conan/profiles/boost: -------------------------------------------------------------------------------- 1 | [options] 2 | boost/*:asio_no_deprecated=True 3 | boost/*:bzip2=False 4 | boost/*:filesystem_no_deprecated=True 5 | boost/*:system_no_deprecated=True 6 | boost/*:without_contract=True 7 | boost/*:without_coroutine=True 8 | boost/*:without_fiber=True 9 | boost/*:without_graph=True 10 | boost/*:without_graph_parallel=True 11 | boost/*:without_iostreams=True 12 | boost/*:without_locale=True 13 | boost/*:without_log=True 14 | boost/*:without_math=True 15 | boost/*:without_mpi=True 16 | boost/*:without_python=True 17 | boost/*:without_random=True 18 | boost/*:without_regex=True 19 | boost/*:without_stacktrace=True 20 | boost/*:without_timer=True 21 | boost/*:without_type_erasure=True 22 | boost/*:without_wave=True 23 | boost/*:zlib=False 24 | -------------------------------------------------------------------------------- /include/pichi/net/adapter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_NET_ADAPTER_HPP 2 | #define PICHI_NET_ADAPTER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace pichi::vo { 10 | 11 | struct Ingress; 12 | struct Egress; 13 | 14 | } // namespace pichi::vo 15 | 16 | namespace pichi::api { 17 | 18 | struct IngressHolder; 19 | 20 | } // namespace pichi::api 21 | 22 | namespace pichi::net { 23 | 24 | std::unique_ptr makeIngress(api::IngressHolder&, boost::asio::ip::tcp::socket&&); 25 | std::unique_ptr makeEgress(vo::Egress const&, boost::asio::io_context&); 26 | 27 | } // namespace pichi::net 28 | 29 | #endif // PICHI_NET_ADAPTER_HPP 30 | -------------------------------------------------------------------------------- /src/vo/error.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // Include config.hpp first 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | namespace json = rapidjson; 8 | using Allocator = json::Document::AllocatorType; 9 | 10 | namespace pichi::vo { 11 | 12 | json::Value toJson(Error const& evo, Allocator& alloc) 13 | { 14 | using StringRef = json::Value::StringRefType; 15 | using SizeType = json::SizeType; 16 | 17 | auto error = json::Value{}; 18 | error.SetObject(); 19 | error.AddMember(error::MESSAGE, 20 | StringRef{evo.message_.data(), static_cast(evo.message_.size())}, 21 | alloc); 22 | return error; 23 | } 24 | 25 | } // namespace pichi::vo 26 | -------------------------------------------------------------------------------- /include/pichi/vo/rule.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_VO_RULE_HPP 2 | #define PICHI_VO_RULE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace pichi::vo { 10 | 11 | struct Rule { 12 | std::vector range_ = {}; 13 | std::vector ingress_ = {}; 14 | std::vector type_ = {}; 15 | std::vector pattern_ = {}; 16 | std::vector domain_ = {}; 17 | std::vector country_ = {}; 18 | }; 19 | 20 | extern rapidjson::Value toJson(Rule const&, rapidjson::Document::AllocatorType&); 21 | 22 | extern bool operator==(Rule const&, Rule const&); 23 | 24 | } // namespace pichi::vo 25 | 26 | #endif // PICHI_VO_RULE_HPP 27 | -------------------------------------------------------------------------------- /src/pichi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // Include config.hpp first 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | namespace api = pichi::api; 11 | namespace asio = boost::asio; 12 | 13 | static asio::io_context io{1}; 14 | 15 | int pichi_run_server(char const* bind, uint16_t port, char const* mmdb) 16 | { 17 | try { 18 | pichi::assertFalse(bind == nullptr); 19 | pichi::assertFalse(mmdb == nullptr); 20 | auto server = api::Server{io, mmdb}; 21 | server.listen(bind, port); 22 | io.run(); 23 | return 0; 24 | } 25 | catch (exception const& e) { 26 | cout << "ERROR: " << e.what() << endl; 27 | return -1; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /include/pichi/net/direct.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_NET_DIRECT_HPP 2 | #define PICHI_NET_DIRECT_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace pichi::net { 8 | 9 | class DirectAdapter : public Egress { 10 | private: 11 | using Socket = boost::asio::ip::tcp::socket; 12 | 13 | public: 14 | DirectAdapter(boost::asio::io_context&); 15 | ~DirectAdapter() override = default; 16 | 17 | public: 18 | size_t recv(MutableBuffer, Yield) override; 19 | void send(ConstBuffer, Yield) override; 20 | void close(Yield) override; 21 | bool readable() const override; 22 | bool writable() const override; 23 | void connect(Endpoint const&, ResolveResults, Yield) override; 24 | 25 | private: 26 | Socket socket_; 27 | }; 28 | 29 | } // namespace pichi::net 30 | 31 | #endif // PICHI_NET_DIRECT_HPP 32 | -------------------------------------------------------------------------------- /cmake/FindBoringSSL.cmake: -------------------------------------------------------------------------------- 1 | find_package(OpenSSL QUIET) 2 | 3 | if(OpenSSL_FOUND) 4 | find_file(OPENSSL_BASE_H openssl/base.h PATHS ${OPENSSL_INCLUDE_DIR} REQUIRED) 5 | file(STRINGS ${OPENSSL_BASE_H} VERSION_LINE REGEX "^#define BORINGSSL_API_VERSION [0-9]+$") 6 | 7 | if(VERSION_LINE) 8 | string(REGEX REPLACE "^#define BORINGSSL_API_VERSION ([0-9]+)$" "\\1" OPENSSL_VERSION ${VERSION_LINE}) 9 | unset(VERSION_LINE) 10 | endif() 11 | endif() 12 | 13 | include(FindPackageHandleStandardArgs) 14 | find_package_handle_standard_args(BoringSSL 15 | REQUIRED_VARS OPENSSL_SSL_LIBRARY OPENSSL_SSL_LIBRARIES 16 | OPENSSL_CRYPTO_LIBRARY OPENSSL_CRYPTO_LIBRARIES 17 | OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES 18 | VERSION_VAR OPENSSL_VERSION 19 | ) 20 | 21 | add_library(BoringSSL::Crypto ALIAS OpenSSL::Crypto) 22 | add_library(BoringSSL::SSL ALIAS OpenSSL::SSL) 23 | -------------------------------------------------------------------------------- /test/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | namespace pichi::unit_test { 10 | 11 | static boost::asio::io_context io; 12 | static boost::asio::detail::Pull* pPull = nullptr; 13 | static boost::asio::detail::Push* pPush = nullptr; 14 | static boost::asio::detail::YieldState* pState = nullptr; 15 | boost::asio::yield_context gYield = {io.get_executor(), *pState, *pPush, *pPull}; 16 | 17 | vector str2vec(string_view s) { return {cbegin(s), cend(s)}; } 18 | 19 | vector hex2bin(string_view hex) 20 | { 21 | auto v = vector(hex.size() / 2, 0); 22 | sodium_hex2bin(v.data(), v.size(), hex.data(), hex.size(), nullptr, nullptr, nullptr); 23 | return v; 24 | } 25 | 26 | } // namespace pichi::unit_test 27 | -------------------------------------------------------------------------------- /include/pichi/api/egress_manager.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_API_EGRESS_MANAGER_HPP 2 | #define PICHI_API_EGRESS_MANAGER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace pichi::api { 11 | 12 | class EgressManager { 13 | public: 14 | using VO = vo::Egress; 15 | 16 | private: 17 | using Container = std::map>; 18 | using ConstIterator = typename Container::const_iterator; 19 | 20 | public: 21 | EgressManager(); 22 | 23 | void update(std::string const&, VO); 24 | void erase(std::string_view); 25 | 26 | ConstIterator begin() const noexcept; 27 | ConstIterator end() const noexcept; 28 | ConstIterator find(std::string_view) const; 29 | 30 | private: 31 | Container c_; 32 | }; 33 | 34 | } // namespace pichi::api 35 | 36 | #endif // PICHI_API_EGRESS_MANAGER_HPP 37 | -------------------------------------------------------------------------------- /include/pichi/net/reject.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PICHI_NET_REJECT_HPP 2 | #define PICHI_NET_REJECT_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace pichi::net { 8 | 9 | class RejectEgress : public Egress { 10 | private: 11 | using Timer = boost::asio::system_timer; 12 | 13 | public: 14 | explicit RejectEgress(boost::asio::io_context&); 15 | explicit RejectEgress(boost::asio::io_context&, uint16_t); 16 | ~RejectEgress() override = default; 17 | 18 | [[noreturn]] size_t recv(MutableBuffer, Yield) override; 19 | [[noreturn]] void send(ConstBuffer, Yield) override; 20 | void close(Yield) override; 21 | [[noreturn]] bool readable() const override; 22 | [[noreturn]] bool writable() const override; 23 | void connect(Endpoint const& remote, ResolveResults next, Yield) override; 24 | 25 | private: 26 | Timer t_; 27 | }; 28 | 29 | } // namespace pichi::net 30 | 31 | #endif // PICHI_NET_REJECT_HPP 32 | -------------------------------------------------------------------------------- /src/net/direct.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // Include config.hpp first 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | namespace asio = boost::asio; 9 | 10 | namespace pichi::net { 11 | 12 | DirectAdapter::DirectAdapter(asio::io_context& io) : socket_{io} {} 13 | 14 | size_t DirectAdapter::recv(MutableBuffer buf, Yield yield) { return readSome(socket_, buf, yield); } 15 | 16 | void DirectAdapter::send(ConstBuffer buf, Yield yield) { write(socket_, buf, yield); } 17 | 18 | void DirectAdapter::close(Yield yield) { pichi::net::close(socket_, yield); } 19 | 20 | bool DirectAdapter::readable() const { return socket_.is_open(); } 21 | 22 | bool DirectAdapter::writable() const { return socket_.is_open(); } 23 | 24 | void DirectAdapter::connect(Endpoint const&, ResolveResults next, Yield yield) 25 | { 26 | pichi::net::connect(next, socket_, yield); 27 | } 28 | 29 | } // namespace pichi::net 30 | -------------------------------------------------------------------------------- /docker/pichi.dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 AS Builder 2 | 3 | ARG FINGERPRINT="false" 4 | ARG VERSION=latest 5 | ENV SRC=/tmp/pichi 6 | 7 | RUN apk add --no-cache cmake g++ git go jq linux-headers make perl py3-pip 8 | RUN pip install --break-system-packages conan 9 | 10 | RUN --mount="target=${SRC}" < 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace pichi::vo { 12 | 13 | struct Egress { 14 | using Credential = 15 | std::variant; 16 | using Option = std::variant; 17 | 18 | AdapterType type_; 19 | std::optional server_; 20 | std::optional credential_; 21 | std::optional