Repository: lxrite/azure-http-proxy Branch: master Commit: c90005d35cab Files: 35 Total size: 144.4 KB Directory structure: gitextract_3nt7pewi/ ├── .github/ │ └── workflows/ │ └── cmake.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.md ├── example/ │ ├── client.json │ └── server.json └── src/ ├── authentication.cpp ├── authentication.hpp ├── configs/ │ └── ahp_mbedtls_config.h ├── encrypt.hpp ├── hash_utils.cpp ├── hash_utils.hpp ├── http_chunk_checker.hpp ├── http_header_parser.cpp ├── http_header_parser.hpp ├── http_proxy_client.cpp ├── http_proxy_client.hpp ├── http_proxy_client_config.cpp ├── http_proxy_client_config.hpp ├── http_proxy_client_connection.cpp ├── http_proxy_client_connection.hpp ├── http_proxy_client_main.cpp ├── http_proxy_server.cpp ├── http_proxy_server.hpp ├── http_proxy_server_config.cpp ├── http_proxy_server_config.hpp ├── http_proxy_server_connection.cpp ├── http_proxy_server_connection.hpp ├── http_proxy_server_connection_context.hpp ├── http_proxy_server_main.cpp ├── key_generator.hpp └── version.hpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/cmake.yml ================================================ name: Build on: push: branches: [ "master" ] pull_request: branches: [ "master" ] env: BUILD_TYPE: Release jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Checkout submodules run: git submodule update --init --recursive - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} ================================================ FILE: .gitignore ================================================ # Compiled Object files *.slo *.lo *.o *.obj # Compiled Dynamic libraries *.so *.dylib *.dll # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app /.idea/ /build/ ================================================ FILE: .gitmodules ================================================ [submodule "third_party/jsonxx"] path = third_party/jsonxx url = https://github.com/hjiang/jsonxx.git [submodule "third_party/networking-ts-impl"] path = third_party/networking-ts-impl url = https://github.com/chriskohlhoff/networking-ts-impl.git [submodule "third_party/curi"] path = third_party/curi url = https://github.com/lxrite/curi.git [submodule "third_party/mbedtls"] path = third_party/mbedtls url = https://github.com/Mbed-TLS/mbedtls.git ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 2.6) project(azure-http-proxy) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() include_directories("third_party/networking-ts-impl/include") set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE) set(ENABLE_TESTING OFF CACHE BOOL "" FORCE) set(MBEDTLS_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/configs/ahp_mbedtls_config.h" CACHE STRING "" FORCE) add_subdirectory("third_party/mbedtls") if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU|Clang") target_compile_options(mbedcrypto PRIVATE "-Wno-unreachable-code") endif() if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") add_compile_options("/std:c++17") endif() include_directories("third_party/jsonxx") add_library(jsonxx STATIC third_party/jsonxx/jsonxx.cc) include_directories("third_party/curi/src") add_subdirectory("third_party/curi/src") add_executable(ahpc src/http_proxy_client_main.cpp src/http_proxy_client.cpp src/http_proxy_client_config.cpp src/http_proxy_client_connection.cpp src/hash_utils.cpp) target_link_libraries(ahpc mbedtls jsonxx) add_executable(ahps src/http_proxy_server_main.cpp src/http_proxy_server.cpp src/http_proxy_server_config.cpp src/http_proxy_server_connection.cpp src/http_header_parser.cpp src/hash_utils.cpp src/authentication.cpp) target_link_libraries(ahps mbedtls jsonxx curi) if(UNIX) target_link_libraries(ahpc pthread) target_link_libraries(ahps pthread) endif() if(WIN32) if(MINGW) target_link_libraries(ahpc ws2_32 wsock32) target_link_libraries(ahps ws2_32 wsock32) endif() endif() install(TARGETS ahpc ahps DESTINATION bin) ================================================ FILE: Dockerfile ================================================ FROM alpine:3.18 as builder RUN apk update \ && apk add alpine-sdk cmake linux-headers WORKDIR /ahp COPY . . RUN cmake -B build -DCMAKE_BUILD_TYPE=Release \ && cmake --build build FROM alpine:3.18 RUN apk update && apk add libgcc libstdc++ COPY --from=builder /ahp/build/ahps /usr/local/bin/ahps COPY --from=builder /ahp/build/ahpc /usr/local/bin/ahpc ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2013-2015 limhiaoing Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # azure-http-proxy ## 简介 AHP(Azure Http Proxy)是一款高速、安全、轻量级和跨平台的HTTP代理,使用对称加密算法AES对传输的数据进行加密,使用非对称加密算法RSA传输密钥。 ## 特性 - 一连接一密钥,AHP会对每个连接使用一个随机生成的密钥和初始化向量,避免重复使用同一密钥 - 使用非对称加密算法RSA传输密钥,只需对客户端公开RSA公钥 - 对目标域名的解析在服务端进行,可以解决本地DNS污染的问题 - 服务端同时支持多种数据加密方式,数据加密方式可由客户端任意指定,客户端可以权衡机器性能以及安全需求选择合适的加密方式 - 多线程并发处理,充分利用多处理器的优势,能同时处理成千上万的并发连接 - 多用户支持,允许为每个用户使用独立的auth_key `(1.1及以上版本)` ## 编译和安装 ### 拉取代码 ``` shell $ git clone --recursive https://github.com/lxrite/azure-http-proxy.git ``` ### 编译器 AHP使用了部分C++17特性,所以对编译器的版本有较高要求,下面列出了部分已测试过可以用来编译AHP的编译器 - Microsoft Visual Studio >= 2017 - GCC >= 7.3 - Clang >= 6.0 ### 编译 AHP使用自动化构建工具CMake来实现跨平台构建 - CMake >= 2.8 ```shell $ cd azure-http-proxy $ mkdir build $ cd build $ cmake -DCMAKE_BUILD_TYPE=Release .. $ cmake --build . ``` 如果编译成功会生成ahpc(客户端)和ahps(服务端)。 OpenWrt/LEDE 编译参考 [openwrt-ahp](https://github.com/lxrite/openwrt-ahp) ## 配置和运行 完整的配置示例见这里: https://github.com/lxrite/azure-http-proxy/tree/master/example 注意:不要使用示例配置中的RSA私钥和公钥,因为私钥一公开就是不安全的了。 如果你要运行的是服务端,那么你首先需要生成一对RSA密钥对,AHP支持任意长度不小于1024位的RSA密钥。下面的命令使用openssl生成2048位的私钥和公钥 ```shell $ openssl genrsa -out rsa_private_key.pem 2048 $ openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem ``` 服务端保留私钥并将公钥告诉客户端。 ### 配置服务端 编辑`server.json`文件 ```json { "bind_address": "0.0.0.0", "listen_port": 8090, "rsa_private_key": "-----BEGIN RSA PRIVATE KEY----- ...... -----END RSA PRIVATE KEY-----", "timeout": 240, "workers": 4, "auth": true, "auth_key_list": [ "testing_key", "Bob", "Alice" ] } ``` 字段名 | 描述 | 是否必选 | 默认值 | ----------------|--------------------|------------------|-----------| bind_address | 服务端绑定的IP地址 | 否 | "0.0.0.0" | listen_port | 服务端绑定的端口 | 否 | 8090 | rsa_private_key | RSA私钥 | 是 | 无 | timeout | 超时时间(秒) | 否 | 240 | workers | 并发工作线程数 | 否 | 4 | auth | 启用代理身份验证 | 否 | false | auth_key_list | auth_key列表 | auth为true时必选 | 无 | ### 配置客户端 编辑`client.json`文件 ```json { "proxy_server_address": "127.0.0.1", "proxy_server_port": 8090, "bind_address": "127.0.0.1", "listen_port": 8089, "rsa_public_key": "-----BEGIN PUBLIC KEY----- ...... -----END PUBLIC KEY-----", "cipher": "aes-256-cfb", "timeout": 240, "workers": 2, "auth_key": "testing_key" } ``` 字段名 | 描述 | 是否必选 | 默认值 | ---------------------|----------------------|------------------|---------------| proxy_server_address | 服务端的IP地址或域名 | 是 | 无 | proxy_server_port | 服务端的端口 | 是 | 无 | bind_address | 客户端绑定的IP地址 | 否 | "127.0.0.1" | listen_port | 客户端的监听端口 | 否 | 8089 | rsa_public_key | RSA公钥 | 是 | 无 | cipher | 加密方法 | 否 | "aes-256-cfb" | timeout | 超时时间(秒) | 否 | 240 | workers | 并发工作线程数 | 否 | 2 | auth_key | 用于身份验证的字符串 | 否 | 值为空字符串或没有这个字段时,请求不携带auth_key,仅当server的auth为false时才能成功建立连接| #### 支持的加密方法 - aes-xyz-cfb - aes-xyz-cfb8 (自1.2版本开始不再支持) - aes-xyz-cfb1 (自1.2版本开始不再支持) - aes-xyz-ofb - aes-xyz-ctr 中间的xyz可以为128、192或256。 ## 运行 确定配置无误后就可以运行AHP了。 ### 运行服务端 Linux或其他类Unix系统 ```shell $ ./ahps -c server.json ``` Windows ```shell $ ahps.exe -c server.json ``` ### 运行客户端 Linux或其他类Unix系统 ```shell $ ./ahpc -c client.json ``` Windows ```shell $ ahpc.exe -c client.json ``` ## 使用Docker ```shell # 拉取镜像 docker pull ghcr.io/lxrite/azure-http-proxy:latest # 启动 ahps docker run -d -p 8090:8090 -v $PWD/server.json:/data/ahp/server.json ghcr.io/lxrite/azure-http-proxy ahps -c /data/ahp/server.json ``` ================================================ FILE: example/client.json ================================================ { "proxy_server_address": "127.0.0.1", "proxy_server_port": 8090, "bind_address": "127.0.0.1", "listen_port": 8089, "rsa_public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxPbPBU61RYBI0rUDVso+\nTzkQ7bXO1j4GWxbYZ3nEL6sLbrftv7rpYq5uPLi9DdJ3ZoEUjxlnO+VUOOtm7LpR\nGmWqUQdNnYHuiZU1UuH7pDIXejQwwSC698FB1kwnoxV4LICkiA1a4qucqlnG8nN6\ngBFs3/1K2DuUs0Hg1hZKlkOq/ONR82XGhXkB/HVwmfgQlZpVbWHQDsZiOv1SUnQW\n8Zs6E/JmW6llBkWtsQT9nQ2uzcV1JGzV0ltB4N0CMC8u2zv/LLTSgS4IKrVicAqO\n9TWkGOFmGowV7PpEAEQC1WcBXThLpUYk2QqiSvTTLTdFNmwEH+hKa1ZBPqOcaTA1\ndQIDAQAB\n-----END PUBLIC KEY-----", "cipher": "aes-256-cfb", "timeout": 240, "workers": 2, "auth_key": "testing_key" } ================================================ FILE: example/server.json ================================================ { "bind_address": "0.0.0.0", "listen_port": 8090, "rsa_private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxPbPBU61RYBI0rUDVso+TzkQ7bXO1j4GWxbYZ3nEL6sLbrft\nv7rpYq5uPLi9DdJ3ZoEUjxlnO+VUOOtm7LpRGmWqUQdNnYHuiZU1UuH7pDIXejQw\nwSC698FB1kwnoxV4LICkiA1a4qucqlnG8nN6gBFs3/1K2DuUs0Hg1hZKlkOq/ONR\n82XGhXkB/HVwmfgQlZpVbWHQDsZiOv1SUnQW8Zs6E/JmW6llBkWtsQT9nQ2uzcV1\nJGzV0ltB4N0CMC8u2zv/LLTSgS4IKrVicAqO9TWkGOFmGowV7PpEAEQC1WcBXThL\npUYk2QqiSvTTLTdFNmwEH+hKa1ZBPqOcaTA1dQIDAQABAoIBAQC0FTSSlbQHJ5Nt\nkzLKV39Az9w6es/x8iO9hNW3Rg2px6lcQa6ObkaUgwcnXYD88kFY5wv1CjTo/nRS\n1mf0aSVeDTX7f8HnELUX9eQbM6LwLRxqDu3EpjhL7dZUKKzU3vxRNA06l3tRY7aJ\nyiur4QlPrp7s7JlRuqfqfYEkPJ1VZE5CN/0chY21rD9nK30Y9+Rttq/oIW0NyK74\nOJYgottIoiBRwgc2ZTxXalWq5yKWXBVZZlcTSOxH8lmDY5Z0md2oYfgTBqUUEzlz\nLm9S88+ydBtUVXSlDN4PPh+N3arshFnRZBAxNtxiIGi4PYfYJmb2U6a1lq0nj12g\nKs6Wk0QBAoGBAOiiJBdhy3iL/29Z7floQoa5iQgPxsSh8hv6jgIos7B2n/Nkre68\ngK/ULw9utsj/lq6Lg9amMPv+hW4rbdL88UfQFFcvjixhgqk9RNNvt51Lg03AIM6w\npF+FPWqQOisrQmPgxWnKNoIZsfEayBm/mTPHk6VBIVq7HmeF9hxMjcilAoGBANi/\nes4zzfB7PmvuBJxLHe6YWIqNCk8aS/uZ4gZMGN9stLFOZPeeshhlq4OTu7m37MzD\nD5RERDFA3y+OnpUnsB5nXwYqIslIPQ1H6WeNia/xusE45OthbBu6ZgpDI6TmTQ5S\nAfLJMIaurkxSzi9jtotCtYVQ8iBvXIlVqNuAFlCRAoGAUepONSGNiTwazPXosysA\nSfppAzqy7ihsXWfDu4TjiR6sQgNQr8EWu1NG4vNET9CYGYws91d75jAAggOu312M\nBJKDpxOqx3vqi3d0ldM/35ZofOdLZhyTNMNxFeYFZZANB6htO0wmF3e+zbx1e4OZ\nCb+cANPHT/CVyzRDrkFSp7UCgYAaPjzhQJZ7uoBZUw1N2y55mAqIZAFOiJGQmhYq\nywXr716FZUeGT7miiJTWrol5OBK8zBVTz1wuVntvZ6Y9yvthAwUXWvyxQETcGcvh\n1NpJ6kvBX2EgOsB7Lvtx3KUxLw0/YaCIw+FTPU0vQotiY4FTpTUTOjTGxpT+r9dt\nFDofcQKBgCy5lnl7oAEK7sPNSxjyi1jELBcMl6bcE6sLkyXli7gpQn33NEFKEM16\nSfKNttCJFKx+3LtJNjMZ6xKQsEGHAQTXu9MxDd+g16ws8W+tIeMLf+Hn+sI+UFND\nfToIgs+zqOC67B/MVPDtRkJskhcUFRjqmcFGJIlkK9TiYHQm1ldW\n-----END RSA PRIVATE KEY-----", "timeout": 240, "workers": 4, "auth": true, "auth_key_list": [ "testing_key", "Bob", "Alice", "+ZI1w$u9N65lTw*nL@$", "SoHPa4xNMBWoXxSOp+6snUtqtdXFH(MO" ] } ================================================ FILE: src/authentication.cpp ================================================ /* * authentication.cpp: * * Copyright (C) 2015-2023 Light Lin All Rights Reserved. * */ #include #include #include "authentication.hpp" #include "hash_utils.hpp" namespace azure_proxy { authentication::authentication() { } bool authentication::auth(const auth_key_hash_t& auth_key_hash) const { return this->auth_keys_map.find(auth_key_hash) != this->auth_keys_map.end(); } void authentication::add_auth_key(const std::string& auth_key) { auto auth_key_hash = hash_utils::sha256(reinterpret_cast(auth_key.data()), auth_key.size()); this->auth_keys_map[auth_key_hash] = auth_key; } void authentication::remove_all_auth_keys() { this->auth_keys_map.clear(); } authentication& authentication::get_instance() { static authentication instance; return instance; } } // namespace azure_proxy ================================================ FILE: src/authentication.hpp ================================================ /* * authentication.hpp: * * Copyright (C) 2015-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_AUTHENTICATION_HPP #define AZURE_AUTHENTICATION_HPP #include #include #include namespace azure_proxy { using auth_key_hash_t = std::array; class authentication { std::map auth_keys_map; private: authentication(); public: bool auth(const auth_key_hash_t& auth_key_hash) const; void add_auth_key(const std::string& auth_key); void remove_all_auth_keys(); static authentication& get_instance(); }; } // namespace azure_proxy #endif ================================================ FILE: src/configs/ahp_mbedtls_config.h ================================================ #define MBEDTLS_HAVE_TIME #define MBEDTLS_HAVE_TIME_DATE #define MBEDTLS_CIPHER_MODE_CFB #define MBEDTLS_CIPHER_MODE_CTR #define MBEDTLS_CIPHER_MODE_OFB #define MBEDTLS_ERROR_STRERROR_DUMMY #define MBEDTLS_AES_C #define MBEDTLS_ASN1_PARSE_C #define MBEDTLS_BASE64_C #define MBEDTLS_CIPHER_C #define MBEDTLS_ENTROPY_C #define MBEDTLS_PEM_PARSE_C #define MBEDTLS_PLATFORM_C #define MBEDTLS_SHA1_C #define MBEDTLS_SHA256_C #define MBEDTLS_TIMING_C #define MBEDTLS_VERSION_C #define MBEDTLS_MD_C #define MBEDTLS_OID_C #define MBEDTLS_BIGNUM_C #define MBEDTLS_RSA_C #define MBEDTLS_PK_C #define MBEDTLS_PK_PARSE_C #define MBEDTLS_PKCS1_V21 #define MBEDTLS_CTR_DRBG_C ================================================ FILE: src/encrypt.hpp ================================================ /* * encrypt.hpp: * * Copyright (C) 2014-2024 Light Lin All Rights Reserved. * */ #ifndef AZURE_ENCRYPT_HPP #define AZURE_ENCRYPT_HPP #include #include #include #include #include #include #include #include #include #include #include namespace azure_proxy { class stream_encryptor { public: virtual void encrypt(const unsigned char* in, unsigned char* out, std::size_t length) = 0; virtual ~stream_encryptor() {} }; class stream_decryptor { public: virtual void decrypt(const unsigned char* in, unsigned char* out, std::size_t length) = 0; virtual ~stream_decryptor() {} }; class copy_encryptor : public stream_encryptor { public: copy_encryptor() {}; virtual void encrypt(const unsigned char* in, unsigned char* out, std::size_t length) { assert(in && out); std::memcpy(out, in, length); } virtual ~copy_encryptor() {} }; class copy_decryptor : public stream_decryptor { public: copy_decryptor() {}; virtual void decrypt(const unsigned char* in, unsigned char* out, std::size_t length) { assert(in && out); std::memcpy(out, in, length); } virtual ~copy_decryptor() {} }; class aes_stream_encryptor : public stream_encryptor { mbedtls_cipher_context_t aes_ctx_; public: aes_stream_encryptor(const unsigned char* key, const mbedtls_cipher_info_t* cipher_info, unsigned char* ivec) { assert(key && cipher_info && ivec); mbedtls_cipher_init(&aes_ctx_); std::unique_ptr auto_free(&aes_ctx_, mbedtls_cipher_free); int ret = mbedtls_cipher_setup(&aes_ctx_, cipher_info); if (ret != 0) { std::cerr << "mbedtls_cipher_setup error: " << ret << std::endl; throw std::runtime_error("mbedtls_cipher_setup error"); } ret = mbedtls_cipher_setkey(&aes_ctx_, key, mbedtls_cipher_info_get_key_bitlen(cipher_info), MBEDTLS_ENCRYPT); if (ret != 0) { std::cerr << "mbedtls_cipher_setkey error: " << ret << std::endl; throw std::runtime_error("mbedtls_cipher_setkey error"); } ret = mbedtls_cipher_set_iv(&aes_ctx_, ivec, mbedtls_cipher_info_get_iv_size(cipher_info)); if (ret != 0) { std::cerr << "mbedtls_cipher_set_iv error: " << ret << std::endl; throw std::runtime_error("mbedtls_cipher_set_iv error"); } auto_free.release(); } virtual void encrypt(const unsigned char* in, unsigned char* out, std::size_t length) override { assert(in && out); std::size_t out_len = 0; int ret = mbedtls_cipher_update(&aes_ctx_, in, length, out, &out_len); if (ret != 0) { std::cerr << "mbedtls_cipher_update error: " << ret << std::endl; throw std::runtime_error("mbedtls_cipher_update error"); } assert(out_len == length); } virtual ~aes_stream_encryptor() override { mbedtls_cipher_free(&aes_ctx_); } }; class aes_stream_decryptor : public stream_decryptor { mbedtls_cipher_context_t aes_ctx_; public: aes_stream_decryptor(const unsigned char* key, const mbedtls_cipher_info_t* cipher_info, unsigned char* ivec) { assert(key && cipher_info && ivec); mbedtls_cipher_init(&aes_ctx_); std::unique_ptr auto_free(&aes_ctx_, mbedtls_cipher_free); int ret = mbedtls_cipher_setup(&aes_ctx_, cipher_info); if (ret != 0) { std::cerr << "mbedtls_cipher_setup error: " << ret << std::endl; throw std::runtime_error("mbedtls_cipher_setup error"); } ret = mbedtls_cipher_setkey(&aes_ctx_, key, mbedtls_cipher_info_get_key_bitlen(cipher_info), MBEDTLS_DECRYPT); if (ret != 0) { std::cerr << "mbedtls_cipher_setkey error: " << ret << std::endl; throw std::runtime_error("mbedtls_cipher_setkey error"); } ret = mbedtls_cipher_set_iv(&aes_ctx_, ivec, mbedtls_cipher_info_get_iv_size(cipher_info)); if (ret != 0) { std::cerr << "mbedtls_cipher_set_iv error: " << ret << std::endl; throw std::runtime_error("mbedtls_cipher_set_iv error"); } auto_free.release(); } virtual void decrypt(const unsigned char* in, unsigned char* out, std::size_t length) override { assert(in && out); std::size_t out_len = 0; int ret = mbedtls_cipher_update(&aes_ctx_, in, length, out, &out_len); if (ret != 0) { std::cerr << "mbedtls_cipher_update error: " << ret << std::endl; throw std::runtime_error("mbedtls_cipher_update error"); } assert(out_len == length); } virtual ~aes_stream_decryptor() override { mbedtls_cipher_free(&aes_ctx_); } }; static const mbedtls_cipher_info_t* aes_cfb128_cipher(std::size_t key_bits) { assert(key_bits == 128 || key_bits == 192 || key_bits == 256); mbedtls_cipher_type_t cipher_type; switch (key_bits) { case 128: cipher_type = mbedtls_cipher_type_t::MBEDTLS_CIPHER_AES_128_CFB128; break; case 192: cipher_type = mbedtls_cipher_type_t::MBEDTLS_CIPHER_AES_192_CFB128; break; default: cipher_type = mbedtls_cipher_type_t::MBEDTLS_CIPHER_AES_256_CFB128; } return mbedtls_cipher_info_from_type(cipher_type); } static const mbedtls_cipher_info_t* aes_ofb128_cipher(std::size_t key_bits) { assert(key_bits == 128 || key_bits == 192 || key_bits == 256); mbedtls_cipher_type_t cipher_type; switch (key_bits) { case 128: cipher_type = mbedtls_cipher_type_t::MBEDTLS_CIPHER_AES_128_OFB; break; case 192: cipher_type = mbedtls_cipher_type_t::MBEDTLS_CIPHER_AES_192_OFB; break; default: cipher_type = mbedtls_cipher_type_t::MBEDTLS_CIPHER_AES_256_OFB; } return mbedtls_cipher_info_from_type(cipher_type); } static const mbedtls_cipher_info_t* aes_ctr128_cipher(std::size_t key_bits) { assert(key_bits == 128 || key_bits == 192 || key_bits == 256); mbedtls_cipher_type_t cipher_type; switch (key_bits) { case 128: cipher_type = mbedtls_cipher_type_t::MBEDTLS_CIPHER_AES_128_CTR; break; case 192: cipher_type = mbedtls_cipher_type_t::MBEDTLS_CIPHER_AES_192_CTR; break; default: cipher_type = mbedtls_cipher_type_t::MBEDTLS_CIPHER_AES_256_CTR; } return mbedtls_cipher_info_from_type(cipher_type); } class aes_cfb128_encryptor : public aes_stream_encryptor { public: aes_cfb128_encryptor(const unsigned char* key, std::size_t key_bits, unsigned char* ivec) : aes_stream_encryptor(key, aes_cfb128_cipher(key_bits), ivec) { } }; class aes_cfb128_decryptor : public aes_stream_decryptor { public: aes_cfb128_decryptor(const unsigned char* key, std::size_t key_bits, unsigned char* ivec) : aes_stream_decryptor(key, aes_cfb128_cipher(key_bits), ivec) { } }; class aes_ofb128_encryptor : public aes_stream_encryptor { public: aes_ofb128_encryptor(const unsigned char* key, std::size_t key_bits, unsigned char* ivec) : aes_stream_encryptor(key, aes_ofb128_cipher(key_bits), ivec) { } }; class aes_ofb128_decryptor : public aes_stream_decryptor { public: aes_ofb128_decryptor(const unsigned char* key, std::size_t key_bits, unsigned char* ivec) : aes_stream_decryptor(key, aes_ofb128_cipher(key_bits), ivec) { } }; class aes_ctr128_encryptor : public aes_stream_encryptor { public: aes_ctr128_encryptor(const unsigned char* key, std::size_t key_bits, unsigned char* ivec) : aes_stream_encryptor(key, aes_ctr128_cipher(key_bits), ivec) { } }; class aes_ctr128_decryptor : public aes_stream_decryptor { public: aes_ctr128_decryptor(const unsigned char* key, std::size_t key_bits, unsigned char* ivec) : aes_stream_decryptor(key, aes_ctr128_cipher(key_bits), ivec) { } }; class rsa { bool is_pub_key_; mbedtls_pk_context pk_; public: rsa(const std::string& key) { if (key.size() > 26 && std::equal(key.begin(), key.begin() + 26, "-----BEGIN PUBLIC KEY-----")) { is_pub_key_ = true; } else if (key.size() > 31 && std::equal(key.begin(), key.begin() + 31, "-----BEGIN RSA PRIVATE KEY-----")) { is_pub_key_ = false; } else { throw std::invalid_argument("invalid RSA key"); } mbedtls_pk_init(&pk_); std::unique_ptr auto_free_pk(&pk_, mbedtls_pk_free); int ret = 0; if (is_pub_key_) { ret = mbedtls_pk_parse_public_key(&pk_, reinterpret_cast(key.c_str()), key.size() + 1); } else { mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&ctr_drbg); std::unique_ptr auto_free_entropy(&entropy, mbedtls_entropy_free); std::unique_ptr auto_free_ctr_drbg(&ctr_drbg, mbedtls_ctr_drbg_free); int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, nullptr, 0); if (ret != 0) { std::cerr << "mbedtls_ctr_drbg_seed error: " << ret << std::endl; throw std::runtime_error("mbedtls_ctr_drbg_seed error"); } ret = mbedtls_pk_parse_key(&pk_, reinterpret_cast(key.c_str()), key.size() + 1, nullptr, 0, mbedtls_ctr_drbg_random, &ctr_drbg); } if (ret != 0) { if (is_pub_key_) { std::cerr << "mbedtls_pk_parse_public_key error: " << ret << std::endl; } else { std::cerr << "mbedtls_pk_parse_key error: " << ret << std::endl; } throw std::invalid_argument("invalid RSA key"); } auto pk_type = mbedtls_pk_get_type(&pk_); if (pk_type != MBEDTLS_PK_RSA) { std::cerr << "mbedtls_pk_get_type: " << pk_type << std::endl; throw std::invalid_argument("invalid RSA key"); } ret = mbedtls_rsa_set_padding(mbedtls_pk_rsa(pk_), MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1); if (ret != 0) { mbedtls_pk_free(&pk_); std::cerr << "mbedtls_rsa_set_padding error: " << ret << std::endl; throw std::runtime_error("mbedtls_rsa_set_padding error"); } auto_free_pk.release(); } ~rsa() { mbedtls_pk_free(&pk_); } int encrypt(int flen, unsigned char* from, unsigned char* to) { assert(from && to); mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&ctr_drbg); std::unique_ptr auto_free_entropy(&entropy, mbedtls_entropy_free); std::unique_ptr auto_free_ctr_drbg(&ctr_drbg, mbedtls_ctr_drbg_free); int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, nullptr, 0); if (ret != 0) { std::cerr << "mbedtls_ctr_drbg_seed error: " << ret << std::endl; return 0; } ret = mbedtls_rsa_pkcs1_encrypt(mbedtls_pk_rsa(pk_), mbedtls_ctr_drbg_random, &ctr_drbg, flen, from, to); if (ret != 0) { std::cerr << "mbedtls_rsa_pkcs1_encrypt error: " << ret << std::endl; return 0; } return modulus_size(); } int decrypt(int flen, unsigned char* from, unsigned char* to) { assert(from && to); mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&ctr_drbg); std::unique_ptr auto_free_entropy(&entropy, mbedtls_entropy_free); std::unique_ptr auto_free_ctr_drbg(&ctr_drbg, mbedtls_ctr_drbg_free); int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, nullptr, 0); if (ret != 0) { std::cerr << "mbedtls_ctr_drbg_seed error: " << ret << std::endl; return 0; } std::size_t out_len = 0; ret = mbedtls_rsa_pkcs1_decrypt(mbedtls_pk_rsa(pk_), mbedtls_ctr_drbg_random, &ctr_drbg, &out_len, from, to, modulus_size()); if (ret != 0) { std::cerr << "mbedtls_rsa_pkcs1_decrypt error: " << ret << std::endl; return 0; } return static_cast(out_len); } int modulus_size() const { return mbedtls_pk_get_bitlen(&pk_) / 8; } }; } // namespace azure_proxy #endif ================================================ FILE: src/hash_utils.cpp ================================================ /* * hash_utils.cpp: * * Copyright (C) 2023-2024 Light Lin All Rights Reserved. * */ #include "hash_utils.hpp" #include "mbedtls/sha256.h" namespace azure_proxy { namespace hash_utils { std::array sha256(const unsigned char* data, std::size_t count) { std::array result; mbedtls_sha256_context ctx; mbedtls_sha256_init(&ctx); mbedtls_sha256_starts(&ctx, 0); mbedtls_sha256_update(&ctx, data, count); mbedtls_sha256_finish(&ctx, result.data()); mbedtls_sha256_free(&ctx); return result; } } // namespace hash_utils } // namespace azure_proxy ================================================ FILE: src/hash_utils.hpp ================================================ /* * hash_utils.hpp: * * Copyright (C) 2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_HASH_UTILS_HPP #define AZURE_HASH_UTILS_HPP #include namespace azure_proxy { namespace hash_utils { std::array sha256(const unsigned char* data, std::size_t count); } // namespace hash_utils } // namespace azure_proxy #endif // AZURE_HASH_UTILS_HPP ================================================ FILE: src/http_chunk_checker.hpp ================================================ /* * http_chunk_checker.hpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_HTTP_CHUNCK_CHCKER_HPP #define AZURE_HTTP_CHUNCK_CHCKER_HPP #include #include #include #include namespace azure_proxy { enum class http_chunk_check_state { chunk_size_start, chunk_size, chunk_ext, chunk_size_cr, chunk_data, chunk_data_cr, chunk_last, chunk_last_cr, chunk_complete, chunk_check_failed }; class http_chunk_checker { http_chunk_check_state state; std::uint32_t current_chunk_size; std::uint32_t current_chunk_size_has_read; public: http_chunk_checker() : state(http_chunk_check_state::chunk_size_start), current_chunk_size(0), current_chunk_size_has_read(0) {} bool is_complete() const { return this->state == http_chunk_check_state::chunk_complete; } bool is_fail() const { return this->state == http_chunk_check_state::chunk_check_failed; } template bool check(ForwardIterator begin, ForwardIterator end) { static_assert(std::is_same::value_type>::value || std::is_same::value_type>::value || std::is_same::value_type>::value, "error"); assert(!this->is_fail()); for (auto iter = begin; iter != end; ++iter) { switch (this->state) { case http_chunk_check_state::chunk_size_start: if (std::isxdigit(static_cast(*iter))) { this->current_chunk_size = (*iter) >= 'A' ? std::toupper(static_cast(*iter)) - 'A' + 10 : *iter - '0'; this->current_chunk_size_has_read = 0; this->state = http_chunk_check_state::chunk_size; continue; } break; case http_chunk_check_state::chunk_size: if (std::isxdigit(static_cast(*iter))) { this->current_chunk_size = this->current_chunk_size * 16 + ((*iter) >= 'A' ? std::toupper(static_cast(*iter)) - 'A' + 10 : *iter - '0'); continue; } else if (*iter == ';' || *iter == ' ') { this->state = http_chunk_check_state::chunk_ext; continue; } else if (*iter == '\r') { this->state = http_chunk_check_state::chunk_size_cr; continue; } break; case http_chunk_check_state::chunk_ext: if (*iter == '\r') { this->state = http_chunk_check_state::chunk_size_cr; } continue; case http_chunk_check_state::chunk_size_cr: if (*iter == '\n') { if (this->current_chunk_size == 0) { this->state = http_chunk_check_state::chunk_last; } else { this->state = http_chunk_check_state::chunk_data; } continue; } break; case http_chunk_check_state::chunk_data: if (this->current_chunk_size_has_read < this->current_chunk_size) { ++this->current_chunk_size_has_read; continue; } else { if (*iter == '\r') { this->state = http_chunk_check_state::chunk_data_cr; continue; } } break; case http_chunk_check_state::chunk_data_cr: if (*iter == '\n') { this->state = http_chunk_check_state::chunk_size_start; continue; } break; case http_chunk_check_state::chunk_last: if (*iter == '\r') { this->state = http_chunk_check_state::chunk_last_cr; continue; } break; case http_chunk_check_state::chunk_last_cr: if (*iter == '\n') { this->state = http_chunk_check_state::chunk_complete; continue; } break; case http_chunk_check_state::chunk_complete: break; default: assert(false); break; } this->state = http_chunk_check_state::chunk_check_failed; return false; } return true; } }; } // namespace azure_proxy #endif ================================================ FILE: src/http_header_parser.cpp ================================================ /* * http_header_parser.cpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #include #include #include #include "http_header_parser.hpp" #include namespace azure_proxy { http_request_header::http_request_header() : _port(80) { } const std::string& http_request_header::method() const { return this->_method; } const std::string& http_request_header::scheme() const { return this->_scheme; } const std::string& http_request_header::host() const { return this->_host; } unsigned short http_request_header::port() const { return this->_port; } const std::string& http_request_header::path_and_query() const { return this->_path_and_query; } const std::string& http_request_header::http_version() const { return this->_http_version; } std::optional http_request_header::get_header_value(const std::string& name) const { auto iter = this->_headers_map.find(name); if (iter == this->_headers_map.end()) { return std::nullopt; } return std::get<1>(*iter); } std::size_t http_request_header::erase_header(const std::string& name) { return this->_headers_map.erase(name); } const http_headers_container& http_request_header::get_headers_map() const { return this->_headers_map; } http_response_header::http_response_header() { } const std::string& http_response_header::http_version() const { return this->_http_version; } unsigned int http_response_header::status_code() const { return this->_status_code; } const std::string& http_response_header::status_description() const { return this->_status_description; } std::optional http_response_header::get_header_value(const std::string& name) const { auto iter = this->_headers_map.find(name); if (iter == this->_headers_map.end()) { return std::nullopt; } return std::get<1>(*iter); } std::size_t http_response_header::erase_header(const std::string& name) { return this->_headers_map.erase(name); } const http_headers_container& http_response_header::get_headers_map() const { return this->_headers_map; } http_headers_container http_header_parser::parse_headers(std::string::const_iterator begin, std::string::const_iterator end) { http_headers_container headers; auto is_digit = [](char ch) -> bool { return '0' <= ch && ch <= '9'; }; auto is_alpha = [](char ch) -> bool { return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z'); }; auto is_token_char = [&is_alpha, &is_digit](char ch) -> bool { return is_alpha(ch) || is_digit(ch) || ( ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '`' || ch == '*' || ch == '+' || ch == '-' || ch == '.' || ch == '^' || ch == '_' || ch == '|' || ch == '~'); }; enum class parse_header_state { header_field_name_start, header_field_name, header_field_value_left_ows, header_field_value, header_field_cr, header_field_crlf, header_field_crlfcr, header_compelete, header_parse_failed }; parse_header_state state = parse_header_state::header_field_name_start; std::string header_field_name; std::string header_field_value; for (std::string::const_iterator iter = begin; iter != end && state != parse_header_state::header_compelete && state != parse_header_state::header_parse_failed; ++iter) { switch (state) { case parse_header_state::header_field_name_start: if (is_token_char(*iter)) { header_field_name.push_back(*iter); state = parse_header_state::header_field_name; } else if (iter == begin && *iter == '\r') { state = parse_header_state::header_field_crlfcr; } else { state = parse_header_state::header_parse_failed; } break; case parse_header_state::header_field_name: if (is_token_char(*iter) || *iter == ' ') { header_field_name.push_back(*iter); } else if (*iter == ':') { state = parse_header_state::header_field_value_left_ows; } else { state = parse_header_state::header_parse_failed; } break; case parse_header_state::header_field_value_left_ows: if (*iter == ' ' || *iter == '\t') { continue; } else if (*iter == '\r') { state = parse_header_state::header_field_cr; } else { header_field_value.push_back(*iter); state = parse_header_state::header_field_value; } break; case parse_header_state::header_field_value: if (*iter == '\r') { state = parse_header_state::header_field_cr; } else { header_field_value.push_back(*iter); } break; case parse_header_state::header_field_cr: if (*iter == '\n') { state = parse_header_state::header_field_crlf; } else { state = parse_header_state::header_parse_failed; } break; case parse_header_state::header_field_crlf: if (*iter == ' ' || *iter == '\t') { header_field_value.push_back(*iter); state = parse_header_state::header_field_value; } else { while (!header_field_name.empty() && (header_field_name[header_field_name.size() - 1] == ' ')) { header_field_name.resize(header_field_name.size() - 1); } assert(!header_field_name.empty()); while (!header_field_value.empty() && (header_field_value[header_field_value.size() - 1] == ' ' || (header_field_value[header_field_value.size() - 1] == '\t'))) { header_field_value.resize(header_field_value.size() - 1); } headers.insert(std::make_pair(std::move(header_field_name), std::move(header_field_value))); if (*iter == '\r') { state = parse_header_state::header_field_crlfcr; } else if (is_token_char(*iter)) { header_field_name.push_back(*iter); state = parse_header_state::header_field_name; } else { state = parse_header_state::header_parse_failed; } } break; case parse_header_state::header_field_crlfcr: if (*iter == '\n') { state = parse_header_state::header_compelete; } break; default: assert(false); } } if (state != parse_header_state::header_compelete) { throw std::runtime_error("failed to parse"); } return headers; } std::optional http_header_parser::parse_request_header(std::string::const_iterator begin, std::string::const_iterator end) { auto iter = begin; auto tmp = iter; for (;iter != end && *iter != ' ' && *iter != '\r'; ++iter) ; if (iter == tmp || iter == end || *iter != ' ') return std::nullopt; http_request_header header; header._method = std::string(tmp, iter); tmp = ++iter; for (;iter != end && *iter != ' ' && *iter != '\r'; ++iter) ; if (iter == tmp || iter == end || *iter != ' ') return std::nullopt; auto request_uri = std::string(tmp, iter); if (header.method() == "CONNECT") { std::regex regex("(.+?):(\\d+)"); std::match_results match_results; if (!std::regex_match(request_uri.begin(), request_uri.end(), match_results, regex)) { return std::nullopt; } header._host = match_results[1]; try { header._port = static_cast(std::stoul(std::string(match_results[2]))); } catch (const std::exception&) { return std::nullopt; } } else { struct parse_user_data { std::string scheme; std::string host; unsigned int port = 80; std::string path; std::string query; }; auto user_data = parse_user_data{}; curi_settings settings; curi_default_settings(&settings); settings.scheme_callback = [](void* user_data, const char* scheme, size_t scheme_len) -> int { reinterpret_cast(user_data)->scheme = std::string(scheme, scheme_len); return 1; }; settings.host_callback = [](void* user_data, const char* host, size_t host_len) -> int { reinterpret_cast(user_data)->host = std::string(host, host_len); return 1; }; settings.port_callback = [](void* user_data, unsigned int port) -> int { reinterpret_cast(user_data)->port = port; return 1; }; settings.path_callback = [](void* user_data, const char* path, size_t path_len) -> int { reinterpret_cast(user_data)->path = std::string(path, path_len); return 1; }; settings.query_callback = [](void* user_data, const char* query, size_t query_len) -> int { reinterpret_cast(user_data)->query = std::string(query, query_len); return 1; }; if (curi_status_success != curi_parse_full_uri(request_uri.c_str(), request_uri.size(), &settings, &user_data)) { return std::nullopt; } if (user_data.scheme.empty() || user_data.host.empty() || user_data.path.empty()) { return std::nullopt; } header._scheme = user_data.scheme; header._host = user_data.host; header._port = user_data.port; header._path_and_query = user_data.path; if (!user_data.query.empty()) { header._path_and_query.push_back('?'); header._path_and_query += user_data.query; } } tmp = ++iter; for (;iter != end && *iter != '\r'; ++iter) ; // HTTP/x.y if (iter == end || std::distance(tmp, iter) < 6 || !std::equal(tmp, tmp + 5, "HTTP/")) return std::nullopt; header._http_version = std::string(tmp + 5, iter); ++iter; if (iter == end || *iter != '\n') return std::nullopt; ++iter; try { header._headers_map = parse_headers(iter, end); } catch (const std::exception&) { return std::nullopt; } return header; } std::optional http_header_parser::parse_response_header(std::string::const_iterator begin, std::string::const_iterator end) { auto iter = begin; auto tmp = iter; for (;iter != end && *iter != ' ' && *iter != '\r'; ++iter) ; if (std::distance(tmp, iter) < 6 || iter == end || *iter != ' ' || !std::equal(tmp, tmp + 5, "HTTP/")) return std::nullopt; http_response_header header; header._http_version = std::string(tmp + 5, iter); tmp = ++iter; for (;iter != end && *iter != ' ' && *iter != '\r'; ++iter) ; if (tmp == iter || iter == end) return std::nullopt; try { header._status_code = std::stoul(std::string(tmp, iter)); } catch(const std::exception&) { return std::nullopt; } if (*iter == ' ') { tmp = ++iter; for (;iter != end && *iter != '\r'; ++iter) ; if (iter == end || *iter != '\r') return std::nullopt; header._status_description = std::string(tmp, iter); } if (*iter != '\r') return std::nullopt; if (iter == end || *(++iter) != '\n') return std::nullopt; ++iter; try { header._headers_map = parse_headers(iter, end); } catch (const std::exception&) { return std::nullopt; } return header; } }; // namespace azure_proxy ================================================ FILE: src/http_header_parser.hpp ================================================ /* * http_header_parser.hpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_HTTP_HEADER_PARSER_HPP #define AZURE_HTTP_HEADER_PARSER_HPP #include #include #include #include #include namespace azure_proxy { struct default_filed_name_compare { bool operator() (const std::string& str1, const std::string& str2) const { return std::lexicographical_compare(str1.begin(), str1.end(), str2.begin(), str2.end(), [](const char ch1, const char ch2) -> bool { return std::tolower(static_cast(ch1)) < std::tolower(static_cast(ch2)); }); } }; typedef std::multimap http_headers_container; class http_request_header { friend class http_header_parser; std::string _method; std::string _scheme; std::string _host; unsigned short _port; std::string _path_and_query; std::string _http_version; http_headers_container _headers_map; http_request_header(); public: const std::string& method() const; const std::string& scheme() const; const std::string& host() const; unsigned short port() const; const std::string& path_and_query() const; const std::string& http_version() const; std::optional get_header_value(const std::string& name) const; std::size_t erase_header(const std::string& name); const http_headers_container& get_headers_map() const; }; class http_response_header { friend class http_header_parser; std::string _http_version; unsigned int _status_code; std::string _status_description; http_headers_container _headers_map; http_response_header(); public: const std::string& http_version() const; unsigned int status_code() const; const std::string& status_description() const; std::optional get_header_value(const std::string& name) const; std::size_t erase_header(const std::string& name); const http_headers_container& get_headers_map() const; }; class http_header_parser { static http_headers_container parse_headers(std::string::const_iterator begin, std::string::const_iterator end); public: static std::optional parse_request_header(std::string::const_iterator begin, std::string::const_iterator end); static std::optional parse_response_header(std::string::const_iterator begin, std::string::const_iterator end); }; }; // namespace azure_proxy #endif ================================================ FILE: src/http_proxy_client.cpp ================================================ /* * http_proxy_client.cpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #include #include #include #include #include #include "http_proxy_client.hpp" #include "http_proxy_client_connection.hpp" #include "http_proxy_client_config.hpp" namespace azure_proxy { http_proxy_client::http_proxy_client(net::io_context& io_ctx) : io_ctx(io_ctx), acceptor(io_ctx) { } void http_proxy_client::run() { const auto& config = http_proxy_client_config::get_instance(); net::ip::tcp::endpoint endpoint(net::ip::make_address(config.get_bind_address()), config.get_listen_port()); this->acceptor.open(endpoint.protocol()); #ifndef _WIN32 this->acceptor.set_option(net::ip::tcp::acceptor::reuse_address(true)); #endif this->acceptor.bind(endpoint); this->acceptor.listen(net::socket_base::max_listen_connections); this->start_accept(); std::vector td_vec; for (auto i = 0u; i < config.get_workers(); ++i) { td_vec.emplace_back([this]() { try { this->io_ctx.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } }); } for (auto& td : td_vec) { td.join(); } } void http_proxy_client::start_accept() { this->acceptor.async_accept([this](const std::error_code& error, net::ip::tcp::socket socket) { if (!error) { auto connection = http_proxy_client_connection::create(this->io_ctx, std::move(socket)); connection->start(); } this->start_accept(); }); } } // namespace azure_proxy ================================================ FILE: src/http_proxy_client.hpp ================================================ /* * http_proxy_client.hpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_HTTP_PROXY_CLIENT_HPP #define AZURE_HTTP_PROXY_CLIENT_HPP #include namespace net = std::experimental::net; namespace azure_proxy { class http_proxy_client { net::io_context& io_ctx; net::ip::tcp::acceptor acceptor; public: http_proxy_client(net::io_context& io_ctx); void run(); private: void start_accept(); }; } // namespace azure_proxy #endif ================================================ FILE: src/http_proxy_client_config.cpp ================================================ /* * http_proxy_client_config.cpp: * * Copyright (C) 2013-2024 Light Lin All Rights Reserved. * */ #include #include #include #include #include #ifdef _WIN32 #include #include #endif #include "encrypt.hpp" #include "http_proxy_client_config.hpp" #include namespace azure_proxy { http_proxy_client_config::http_proxy_client_config() {} bool http_proxy_client_config::load_config_data(const std::string& config_data) { bool rollback = true; std::shared_ptr auto_rollback(&rollback, [this](bool* rollback) { if (*rollback) { this->config_map.clear(); } }); jsonxx::Object json_obj; if (!json_obj.parse(config_data)) { std::cerr << "Failed to parse config" << std::endl; return false; } if (!json_obj.has("proxy_server_address")) { std::cerr << "Could not find \"proxy_server_address\" in config or it's value is not a string" << std::endl; return false; } this->config_map["proxy_server_address"] = std::string(json_obj.get("proxy_server_address")); if (!json_obj.has("proxy_server_port")) { std::cerr << "Could not find \"proxy_server_port\" in config or it's value is not a number" << std::endl; return false; } this->config_map["proxy_server_port"] = static_cast(json_obj.get("proxy_server_port")); if (json_obj.has("bind_address")) { this->config_map["bind_address"] = std::string(json_obj.get("bind_address")); } else { this->config_map["bind_address"] = std::string("127.0.0.1"); } if (json_obj.has("listen_port")) { this->config_map["listen_port"] = static_cast(json_obj.get("listen_port")); } else { this->config_map["listen_port"] = static_cast(8089); } if (!json_obj.has("rsa_public_key")) { std::cerr << "Could not find \"rsa_public_key\" in config or it's value is not a string" << std::endl; return false; } const std::string& rsa_public_key = json_obj.get("rsa_public_key"); try { rsa rsa_pub(rsa_public_key); if (rsa_pub.modulus_size() < 128) { std::cerr << "Must use RSA keys of at least 1024 bits" << std::endl; return false; } } catch (const std::exception&) { std::cerr << "The value of rsa_public_key is bad" << std::endl; return false; } this->config_map["rsa_public_key"] = rsa_public_key; if (json_obj.has("cipher")) { std::string cipher = std::string(json_obj.get("cipher")); for (auto& ch : cipher) { ch = std::tolower(static_cast(ch)); } bool is_supported_cipher = false; if (cipher.size() > 3 && std::equal(cipher.begin(), cipher.begin() + 4, "aes-")) { if (cipher.size() > 8 && cipher[7] == '-' && (std::equal(cipher.begin() + 4, cipher.begin() + 7, "128") || std::equal(cipher.begin() + 4, cipher.begin() + 7, "192") || std::equal(cipher.begin() + 4, cipher.begin() + 7, "256") )) { if (std::equal(cipher.begin() + 8, cipher.end(), "cfb") || std::equal(cipher.begin() + 8, cipher.end(), "cfb128") || std::equal(cipher.begin() + 8, cipher.end(), "ofb") || std::equal(cipher.begin() + 8, cipher.end(), "ofb128") || std::equal(cipher.begin() + 8, cipher.end(), "ctr") || std::equal(cipher.begin() + 8, cipher.end(), "ctr128")) { is_supported_cipher = true; } } } if (!is_supported_cipher) { std::cerr << "Unsupported cipher: " << cipher << std::endl; return false; } this->config_map["cipher"] = cipher; } else { this->config_map["cipher"] = std::string("aes-256-cfb"); } if (json_obj.has("timeout")) { int timeout = static_cast(json_obj.get("timeout")); this->config_map["timeout"] = static_cast(timeout < 30 ? 30 : timeout); } else { this->config_map["timeout"] = 240u; } if (json_obj.has("workers")) { int threads = static_cast(json_obj.get("workers")); this->config_map["workers"] = static_cast(threads < 1 ? 1 : (threads > 16 ? 16 : threads)); } else { this->config_map["workers"] = 2u; } std::string auth_key; if (json_obj.has("auth_key")) { auth_key = std::string(json_obj.get("auth_key")); } this->config_map["auth_key"] = auth_key; rollback = false; return true; } bool http_proxy_client_config::load_config(const std::string& config_path) { std::string config_data; #ifdef _WIN32 std::wstring_convert> converter; std::wstring config_file_path = converter.from_bytes(config_path); std::shared_ptr::type> config_file_handle( CreateFileW(config_file_path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL), [](HANDLE native_handle) { if (native_handle != INVALID_HANDLE_VALUE) { CloseHandle(native_handle); } }); if (config_file_handle.get() == INVALID_HANDLE_VALUE) { std::cerr << "Failed to open config file \"client.json\"" << std::endl; return false; } char ch; DWORD size_read = 0; BOOL read_result = ReadFile(config_file_handle.get(), &ch, 1, &size_read, NULL); while (read_result != FALSE && size_read != 0) { config_data.push_back(ch); read_result = ReadFile(config_file_handle.get(), &ch, 1, &size_read, NULL); } if (read_result == FALSE) { std::cerr << "Failed to read data from config file" << std::endl; return false; } #else std::ifstream ifile(config_path.c_str()); if (!ifile.is_open()) { std::cerr << "Failed to open \"" << config_path << "\"" << std::endl; return false; } char ch; while (ifile.get(ch)) { config_data.push_back(ch); } #endif return this->load_config_data(config_data); } const std::string& http_proxy_client_config::get_proxy_server_address() const { return this->get_config_value("proxy_server_address"); } unsigned short http_proxy_client_config::get_proxy_server_port() const { return this->get_config_value("proxy_server_port"); } const std::string& http_proxy_client_config::get_bind_address() const { return this->get_config_value("bind_address"); } unsigned short http_proxy_client_config::get_listen_port() const { return this->get_config_value("listen_port"); } const std::string& http_proxy_client_config::get_rsa_public_key() const { return this->get_config_value("rsa_public_key"); } const std::string& http_proxy_client_config::get_cipher() const { return this->get_config_value("cipher"); } unsigned int http_proxy_client_config::get_timeout() const { return this->get_config_value("timeout"); } unsigned int http_proxy_client_config::get_workers() const { return this->get_config_value("workers"); } const std::string& http_proxy_client_config::get_auth_key() const { return this->get_config_value("auth_key"); } http_proxy_client_config& http_proxy_client_config::get_instance() { static http_proxy_client_config instance; return instance; } } // namespace azure_proxy ================================================ FILE: src/http_proxy_client_config.hpp ================================================ /* * http_proxy_client_config.hpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_HTTP_PROXY_CLIENT_CONFIG_HPP #define AZURE_HTTP_PROXY_CLIENT_CONFIG_HPP #include #include #include #include #include namespace azure_proxy { class http_proxy_client_config { std::map config_map; private: template T get_config_value(const std::string& key) const { assert(!this->config_map.empty()); auto iter = this->config_map.find(key); if (iter == this->config_map.end()) { throw std::invalid_argument("invalid argument"); } return std::any_cast(iter->second); } http_proxy_client_config(); bool load_config_data(const std::string& config_data); public: bool load_config(const std::string& config_path); const std::string& get_proxy_server_address() const; unsigned short get_proxy_server_port() const; const std::string& get_bind_address() const; unsigned short get_listen_port() const; const std::string& get_rsa_public_key() const; const std::string& get_cipher() const; unsigned int get_timeout() const; unsigned int get_workers() const; const std::string& get_auth_key() const; static http_proxy_client_config& get_instance(); }; } // namespace azure_proxy #endif ================================================ FILE: src/http_proxy_client_connection.cpp ================================================ /* * http_proxy_client_connection.cpp: * * Copyright (C) 2013-2024 Light Lin All Rights Reserved. * */ #include #include #include #include "http_proxy_client_config.hpp" #include "http_proxy_client_connection.hpp" #include "key_generator.hpp" #include "hash_utils.hpp" namespace azure_proxy { http_proxy_client_connection::http_proxy_client_connection(net::io_context& io_ctx, net::ip::tcp::socket&& ua_socket) : strand(io_ctx.get_executor()), user_agent_socket(std::move(ua_socket)), proxy_server_socket(io_ctx), resolver(io_ctx), connection_state(proxy_connection_state::ready), timer(io_ctx), timeout(std::chrono::seconds(http_proxy_client_config::get_instance().get_timeout())) { } http_proxy_client_connection::~http_proxy_client_connection() { } std::shared_ptr http_proxy_client_connection::create(net::io_context& io_ctx, net::ip::tcp::socket&& ua_socket) { return std::shared_ptr(new http_proxy_client_connection(io_ctx, std::move(ua_socket))); } void http_proxy_client_connection::start() { std::array cipher_info_raw; cipher_info_raw.fill(0); // 0 ~ 2 cipher_info_raw[0] = 'A'; cipher_info_raw[1] = 'H'; cipher_info_raw[2] = 'P'; // 3 protocol version // - 0: version 1.0 // - 1: since version 1.1, support 32 bytes auth_key_hash unsigned char protocol_version = 1; const auto &auth_key = http_proxy_client_config::get_instance().get_auth_key(); bool has_auth_key = !auth_key.empty(); if (!has_auth_key) { // Use old protocol to be compatible with version 1.0 protocol_version = 0; } // cipher code // 0x00 aes-128-cfb // 0x01 aes-128-cfb8 // 0x02 aes-128-cfb1 // 0x03 aes-128-ofb // 0x04 aes-128-ctr // 0x05 aes-192-cfb // 0x06 aes-192-cfb8 // 0x07 aes-192-cfb1 // 0x08 aes-192-ofb // 0x09 aes-192-ctr // 0x0A aes-256-cfb // 0x0B aes-256-cfb8 // 0x0C aes-256-cfb1 // 0x0D aes-256-ofb // 0x0E aes-256-ctr unsigned char cipher_code = 0; std::vector ivec(16); std::vector key_vec; const auto& cipher_name = http_proxy_client_config::get_instance().get_cipher(); if (cipher_name.size() > 7 && std::equal(cipher_name.begin(), cipher_name.begin() + 3, "aes")) { // aes assert(cipher_name[3] == '-' && cipher_name[7] == '-'); if (std::strcmp(cipher_name.c_str() + 8, "cfb") == 0 || std::strcmp(cipher_name.c_str() + 8, "cfb128") == 0) { // aes-xxx-cfb if (std::equal(cipher_name.begin() + 4, cipher_name.begin() + 7, "128")) { cipher_code = 0x00; key_vec.resize(128 / 8); } else if (std::equal(cipher_name.begin() + 4, cipher_name.begin() + 7, "192")) { cipher_code = 0x05; key_vec.resize(192 / 8); } else { cipher_code = 0x0A; key_vec.resize(256 / 8); } key_generator::get_instance().generate(ivec.data(), ivec.size()); key_generator::get_instance().generate(key_vec.data(), key_vec.size()); this->encryptor = std::unique_ptr(new aes_cfb128_encryptor(key_vec.data(), key_vec.size() * 8, ivec.data())); this->decryptor = std::unique_ptr(new aes_cfb128_decryptor(key_vec.data(), key_vec.size() * 8, ivec.data())); } else if (std::strcmp(cipher_name.c_str() + 8, "cfb8") == 0) { // aes-xxx-cfb8(deprecated) } else if (std::strcmp(cipher_name.c_str() + 8, "cfb1") == 0) { // aes-xxx-cfb1(deprecated) } else if (std::strcmp(cipher_name.c_str() + 8, "ofb") == 0) { // aes-xxx-ofb if (std::equal(cipher_name.begin() + 4, cipher_name.begin() + 7, "128")) { cipher_code = 0x03; key_vec.resize(128 / 8); } else if (std::equal(cipher_name.begin() + 4, cipher_name.begin() + 7, "192")) { cipher_code = 0x08; key_vec.resize(192 / 8); } else { cipher_code = 0x0D; key_vec.resize(256 / 8); } key_generator::get_instance().generate(ivec.data(), ivec.size()); key_generator::get_instance().generate(key_vec.data(), key_vec.size()); this->encryptor = std::unique_ptr(new aes_ofb128_encryptor(key_vec.data(), key_vec.size() * 8, ivec.data())); this->decryptor = std::unique_ptr(new aes_ofb128_decryptor(key_vec.data(), key_vec.size() * 8, ivec.data())); } else if (std::strcmp(cipher_name.c_str() + 8, "ctr") == 0) { // aes-xxx-ctr if (std::equal(cipher_name.begin() + 4, cipher_name.begin() + 7, "128")) { cipher_code = 0x04; key_vec.resize(128 / 8); } else if (std::equal(cipher_name.begin() + 4, cipher_name.begin() + 7, "192")) { cipher_code = 0x09; key_vec.resize(192 / 8); } else { cipher_code = 0x0E; key_vec.resize(256 / 8); } std::fill(ivec.begin(), ivec.end(), 0); key_generator::get_instance().generate(key_vec.data(), key_vec.size()); this->encryptor = std::unique_ptr(new aes_ctr128_encryptor(key_vec.data(), key_vec.size() * 8, ivec.data())); this->decryptor = std::unique_ptr(new aes_ctr128_decryptor(key_vec.data(), key_vec.size() * 8, ivec.data())); } } if (!this->encryptor || !this->decryptor) { return; } // 3 protocol version cipher_info_raw[3] = protocol_version; if (protocol_version == 0) { // 4 zero // 5 cipher code cipher_info_raw[5] = cipher_code; // 6 zero // 7 ~ 22 ivec std::copy(ivec.cbegin(), ivec.cend(), cipher_info_raw.begin() + 7); // 23 ~ (38/46/54) cipher key std::copy(key_vec.cbegin(), key_vec.cend(), cipher_info_raw.begin() + 23); } else { // 4 auth type // - 0 no authentication // - 1 32 bytes auth_key_hash unsigned char auth_type = has_auth_key ? 1 : 0; cipher_info_raw[4] = auth_type; // 5 cipher code cipher_info_raw[5] = cipher_code; // 6 ~ 37 auth_key_hash if (auth_type == 1) { auto auth_key_hash = hash_utils::sha256(reinterpret_cast(auth_key.data()), auth_key.size()); std::copy(auth_key_hash.cbegin(), auth_key_hash.cend(), cipher_info_raw.begin() + 6); } // 38 ~ 53 ivec std::copy(ivec.cbegin(), ivec.cend(), cipher_info_raw.begin() + 38); // 54 ~ (69/77/85) cipher key std::copy(key_vec.cbegin(), key_vec.cend(), cipher_info_raw.begin() + 54); } rsa rsa_pub(http_proxy_client_config::get_instance().get_rsa_public_key()); if (rsa_pub.modulus_size() < 128) { return; } this->encrypted_cipher_info.resize(rsa_pub.modulus_size()); if(this->encrypted_cipher_info.size() != rsa_pub.encrypt(cipher_info_raw.size(), cipher_info_raw.data(), this->encrypted_cipher_info.data())) { return; } auto self(this->shared_from_this()); const auto& proxy_server_address = http_proxy_client_config::get_instance().get_proxy_server_address(); auto proxy_server_port = std::to_string(http_proxy_client_config::get_instance().get_proxy_server_port()); this->connection_state = proxy_connection_state::resolve_proxy_server_address; this->set_timer(); this->resolver.async_resolve(proxy_server_address, proxy_server_port, net::bind_executor(this->strand, [this, self](const std::error_code& error, net::ip::tcp::resolver::results_type results) { if (this->cancel_timer()) { if (!error) { this->connection_state = proxy_connection_state::connecte_to_proxy_server; this->set_timer(); this->proxy_server_socket.async_connect(*results.cbegin(), net::bind_executor(this->strand, [this, self](const std::error_code& error) { if (this->cancel_timer()) { if (!error) { this->on_connection_established(); } else { this->on_error(error); } } })); } else { this->on_error(error); } } })); } void http_proxy_client_connection::async_read_data_from_user_agent() { auto self(this->shared_from_this()); this->set_timer(); this->user_agent_socket.async_read_some(net::buffer(this->upgoing_buffer_read.data(), this->upgoing_buffer_read.size()), net::bind_executor(this->strand, [this, self](const std::error_code& error, std::size_t bytes_transferred) { if (this->cancel_timer()) { if (!error) { this->encryptor->encrypt(reinterpret_cast(&this->upgoing_buffer_read[0]), reinterpret_cast(&this->upgoing_buffer_write[0]), bytes_transferred); this->async_write_data_to_proxy_server(this->upgoing_buffer_write.data(), 0, bytes_transferred); } else { this->on_error(error); } } })); } void http_proxy_client_connection::async_read_data_from_proxy_server(bool set_timer) { auto self(this->shared_from_this()); if (set_timer) { this->set_timer(); } this->proxy_server_socket.async_read_some(net::buffer(this->downgoing_buffer_read.data(), this->downgoing_buffer_read.size()), net::bind_executor(this->strand, [this, self](const std::error_code& error, std::size_t bytes_transferred) { if (this->cancel_timer()) { if (!error) { this->decryptor->decrypt(reinterpret_cast(&this->downgoing_buffer_read[0]), reinterpret_cast(&this->downgoing_buffer_write[0]), bytes_transferred); this->async_write_data_to_user_agent(this->downgoing_buffer_write.data(), 0, bytes_transferred); } else { this->on_error(error); } } })); } void http_proxy_client_connection::async_write_data_to_user_agent(const char* write_buffer, std::size_t offset, std::size_t size) { auto self(this->shared_from_this()); this->set_timer(); this->user_agent_socket.async_write_some(net::buffer(write_buffer + offset, size), net::bind_executor(this->strand, [this, self, write_buffer, offset, size](const std::error_code& error, std::size_t bytes_transferred) { if (this->cancel_timer()) { if (!error) { if (bytes_transferred < size) { this->async_write_data_to_user_agent(write_buffer, offset + bytes_transferred, size - bytes_transferred); } else { this->async_read_data_from_proxy_server(); } } else { this->on_error(error); } } })); } void http_proxy_client_connection::async_write_data_to_proxy_server(const char* write_buffer, std::size_t offset, std::size_t size) { auto self(this->shared_from_this()); this->set_timer(); this->proxy_server_socket.async_write_some(net::buffer(write_buffer + offset, size), net::bind_executor(this->strand, [this, self, write_buffer, offset, size](const std::error_code& error, std::size_t bytes_transferred) { if (this->cancel_timer()) { if (!error) { if (bytes_transferred < size) { this->async_write_data_to_proxy_server(write_buffer, offset + bytes_transferred, size - bytes_transferred); } else { this->async_read_data_from_user_agent(); } } else { this->on_error(error); } } }) ); } void http_proxy_client_connection::set_timer() { if (this->timer.expires_after(this->timeout) != 0) { assert(false); } auto self(this->shared_from_this()); this->timer.async_wait(net::bind_executor(this->strand, [this, self](const std::error_code& error) { if (error != net::error::operation_aborted) { this->on_timeout(); } })); } bool http_proxy_client_connection::cancel_timer() { std::size_t ret = this->timer.cancel(); assert(ret <= 1); return ret == 1; } void http_proxy_client_connection::on_connection_established() { this->async_write_data_to_proxy_server(reinterpret_cast(this->encrypted_cipher_info.data()), 0, this->encrypted_cipher_info.size()); this->async_read_data_from_proxy_server(false); } void http_proxy_client_connection::on_error(const std::error_code& error) { this->cancel_timer(); std::error_code ec; if (this->proxy_server_socket.is_open()) { this->proxy_server_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->proxy_server_socket.close(ec); } if (this->user_agent_socket.is_open()) { this->user_agent_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->user_agent_socket.close(ec); } } void http_proxy_client_connection::on_timeout() { if (this->connection_state == proxy_connection_state::resolve_proxy_server_address) { this->resolver.cancel(); } else { std::error_code ec; if (this->proxy_server_socket.is_open()) { this->proxy_server_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->proxy_server_socket.close(ec); } if (this->user_agent_socket.is_open()) { this->user_agent_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->user_agent_socket.close(ec); } } } } // namespace azure_proxy ================================================ FILE: src/http_proxy_client_connection.hpp ================================================ /* * http_proxy_client_connection.hpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_HTTP_PROXY_CLIENT_CONNECTION_HPP #define AZURE_HTTP_PROXY_CLIENT_CONNECTION_HPP #include #include #include #include #include #include "encrypt.hpp" const std::size_t BUFFER_LENGTH = 2048; namespace net = std::experimental::net; namespace azure_proxy { class http_proxy_client_connection : public std::enable_shared_from_this { enum class proxy_connection_state { ready, resolve_proxy_server_address, connecte_to_proxy_server, tunnel_transfer }; private: net::strand strand; net::ip::tcp::socket user_agent_socket; net::ip::tcp::socket proxy_server_socket; net::ip::tcp::resolver resolver; proxy_connection_state connection_state; net::basic_waitable_timer timer; std::vector encrypted_cipher_info; std::array upgoing_buffer_read; std::array upgoing_buffer_write; std::array downgoing_buffer_read; std::array downgoing_buffer_write; std::unique_ptr encryptor; std::unique_ptr decryptor; std::chrono::seconds timeout; private: http_proxy_client_connection(net::io_context& io_ctx, net::ip::tcp::socket&& ua_socket); public: ~http_proxy_client_connection(); static std::shared_ptr create(net::io_context& io_ctx, net::ip::tcp::socket&& ua_socket); void start(); private: void async_read_data_from_user_agent(); void async_read_data_from_proxy_server(bool set_timer = true); void async_write_data_to_user_agent(const char* write_buffer, std::size_t offset, std::size_t size); void async_write_data_to_proxy_server(const char* write_buffer, std::size_t offset, std::size_t size); void set_timer(); bool cancel_timer(); void on_connection_established(); void on_error(const std::error_code& error); void on_timeout(); }; } // namespace azure_proxy #endif ================================================ FILE: src/http_proxy_client_main.cpp ================================================ /* * http_proxy_client_main.cpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #include #include #include #include #include #include #include "http_proxy_client.hpp" #include "http_proxy_client_config.hpp" #include "version.hpp" #ifdef _WIN32 #include #include #endif namespace net = std::experimental::net; struct ClientArgs { std::string config_file = "client.json"; }; void print_usage() { #ifdef _WIN32 const char *prog = "ahpc.exe"; #else const char *prog = "ahpc"; #endif std::cout << "Usage: " << prog << " [options]\n\n" << "options:\n" << " -h, --help Show this help message and exit\n" << " -v, --version Print the program version and exit\n" << " -c, --config PATH Configuration file path (default: client.json)\n"; } static ClientArgs parse_args(const std::vector& argv) { std::string arg; bool invalid_param = false; ClientArgs args; for (std::size_t i = 1; i < argv.size(); ++i) { arg = argv[i]; if (arg == "-h" || arg == "--help") { print_usage(); std::exit(EXIT_SUCCESS); } else if (arg == "-v" || arg == "--version") { std::cout << "Version: " << AHP_VERSION_STRING << std::endl; std::exit(EXIT_SUCCESS); } else if (arg == "-c" || arg == "--config") { if (++i >= argv.size()) { invalid_param = true; break; } args.config_file = argv[i]; } else { std::cerr << "Unknown argument: " << arg << std::endl; print_usage(); std::exit(EXIT_FAILURE); } } if (invalid_param) { std::cerr << "Invalid parameter for argument: " << arg << std::endl; std::exit(EXIT_FAILURE); } return args; } static ClientArgs parse_args(int argc, char** argv) { std::vector argv_vec; argv_vec.reserve(argc); #ifdef _WIN32 LPWSTR *wargs = CommandLineToArgvW(GetCommandLineW(), &argc); if (wargs == nullptr) { std::cerr << "Failed to retrieve command line arguments" << std::endl; std::exit(EXIT_FAILURE); } std::wstring_convert> converter; for (std::size_t i = 0; i < argc; ++i) { argv_vec.emplace_back(converter.to_bytes(wargs[i])); } LocalFree(wargs); #else for (std::size_t i = 0; i < argc; ++i) { argv_vec.emplace_back(argv[i]); } #endif return parse_args(argv_vec); } std::weak_ptr wp_io_ctx; static void signal_handler(int signal) { auto io_ctx = wp_io_ctx.lock(); if (io_ctx) { io_ctx->stop(); } } int main(int argc, char** argv) { using namespace azure_proxy; auto args = parse_args(argc, argv); try { auto& config = http_proxy_client_config::get_instance(); if (config.load_config(args.config_file)) { std::cout << "AHP client version " << AHP_VERSION_STRING << std::endl; std::cout << "server address: " << config.get_proxy_server_address() << ':' << config.get_proxy_server_port() << std::endl; std::cout << "local address: " << config.get_bind_address() << ':' << config.get_listen_port() << std::endl; std::cout << "cipher: " << config.get_cipher() << std::endl; auto io_ctx = std::make_shared(); wp_io_ctx = io_ctx; http_proxy_client client(*io_ctx); std::signal(SIGINT, signal_handler); std::signal(SIGTERM, signal_handler); client.run(); } } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } } ================================================ FILE: src/http_proxy_server.cpp ================================================ /* * http_proxy_server.cpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #include #include #include #include #include "http_proxy_server.hpp" #include "http_proxy_server_config.hpp" #include "http_proxy_server_connection.hpp" namespace azure_proxy { http_proxy_server::http_proxy_server(net::io_context& io_ctx) : io_ctx(io_ctx), acceptor(io_ctx) { } void http_proxy_server::run() { const auto& config = http_proxy_server_config::get_instance(); net::ip::tcp::endpoint endpoint(net::ip::make_address(config.get_bind_address()), config.get_listen_port()); this->acceptor.open(endpoint.protocol()); #ifndef _WIN32 this->acceptor.set_option(net::ip::tcp::acceptor::reuse_address(true)); #endif this->acceptor.bind(endpoint); this->acceptor.listen(net::socket_base::max_listen_connections); this->start_accept(); std::vector td_vec; for (auto i = 0u; i < config.get_workers(); ++i) { td_vec.emplace_back([this]() { try { this->io_ctx.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } }); } for (auto& td : td_vec) { td.join(); } } void http_proxy_server::start_accept() { this->acceptor.async_accept([this](const std::error_code& error, net::ip::tcp::socket socket) { if (!error) { auto connection = http_proxy_server_connection::create(this->io_ctx, std::move(socket)); connection->start(); } this->start_accept(); }); } } //namespace azure_proxy ================================================ FILE: src/http_proxy_server.hpp ================================================ /* * http_proxy_server.hpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_HTTP_PROXY_SERVER_HPP #define AZURE_HTTP_PROXY_SERVER_HPP #include namespace net = std::experimental::net; namespace azure_proxy { class http_proxy_server { net::io_context& io_ctx; net::ip::tcp::acceptor acceptor; public: http_proxy_server(net::io_context& io_ctx); void run(); private: void start_accept(); }; } // namespace azure_proxy #endif ================================================ FILE: src/http_proxy_server_config.cpp ================================================ /* * http_proxy_server_config.cpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #include #include #include #include #include #ifdef _WIN32 #include #include #endif #include "authentication.hpp" #include "encrypt.hpp" #include "http_proxy_server_config.hpp" #include namespace azure_proxy { http_proxy_server_config::http_proxy_server_config() { } bool http_proxy_server_config::load_config_data(const std::string& config_data) { bool rollback = true; std::shared_ptr auto_rollback(&rollback, [this](bool* rollback) { if (*rollback) { this->config_map.clear(); authentication::get_instance().remove_all_auth_keys(); } }); jsonxx::Object json_obj; if (!json_obj.parse(config_data)) { std::cerr << "Failed to parse config" << std::endl; return false; } if (json_obj.has("bind_address")) { this->config_map["bind_address"] = std::string(json_obj.get("bind_address")); } else { this->config_map["bind_address"] = std::string("0.0.0.0"); } if (json_obj.has("listen_port")) { this->config_map["listen_port"] = static_cast(json_obj.get("listen_port")); } else { this->config_map["listen_port"] = static_cast(8090); } if (!json_obj.has("rsa_private_key")) { std::cerr << "Could not find \"rsa_private_key\" in config or it's value is not a string" << std::endl; return false; } const std::string& rsa_private_key = json_obj.get("rsa_private_key"); try { rsa rsa_pri(rsa_private_key); if (rsa_pri.modulus_size() < 128) { std::cerr << "Must use RSA keys of at least 1024 bits" << std::endl; return false; } } catch (const std::exception&) { std::cerr << "The value of rsa_private_key is bad" << std::endl; return false; } this->config_map["rsa_private_key"] = rsa_private_key; if (json_obj.has("timeout")) { int timeout = static_cast(json_obj.get("timeout")); this->config_map["timeout"] = static_cast(timeout < 30 ? 30 : timeout); } else { this->config_map["timeout"] = 240u; } if (json_obj.has("workers")) { int threads = static_cast(json_obj.get("workers")); this->config_map["workers"] = static_cast(threads < 1 ? 1 : (threads > 16 ? 16 : threads)); } else { this->config_map["workers"] = 4u; } if (json_obj.has("auth") && json_obj.get("auth")) { this->config_map["auth"] = true; if (!json_obj.has("auth_key_list")) { std::cerr << "Could not find \"auth_key_list\" in config or it's value is not a array" << std::endl; return false; } const jsonxx::Array& auth_key_list = json_obj.get("auth_key_list"); for (size_t i = 0; i < auth_key_list.size(); ++i) { if (!auth_key_list.has(i)) { std::cerr << "The value of \"auth_key_list\" contains unexpected element" << std::endl; return false; } const auto &auth_key = auth_key_list.get(i); if (auth_key.empty()) { std::cerr << "Ignore empty \"auth_key\" at index: " << i << std::endl; continue; } authentication::get_instance().add_auth_key(auth_key); } } else { this->config_map["auth"] = false; } rollback = false; return true; } bool http_proxy_server_config::load_config(const std::string& config_path) { std::string config_data; #ifdef _WIN32 std::wstring_convert> converter; std::wstring config_file_path = converter.from_bytes(config_path); std::shared_ptr::type> config_file_handle( CreateFileW(config_file_path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL), [](HANDLE native_handle) { if (native_handle != INVALID_HANDLE_VALUE) { CloseHandle(native_handle); } }); if (config_file_handle.get() == INVALID_HANDLE_VALUE) { std::cerr << "Failed to open config file \"server.json\"" << std::endl; return false; } char ch; DWORD size_read = 0; BOOL read_result = ReadFile(config_file_handle.get(), &ch, 1, &size_read, NULL); while (read_result != FALSE && size_read != 0) { config_data.push_back(ch); read_result = ReadFile(config_file_handle.get(), &ch, 1, &size_read, NULL); } if (read_result == FALSE) { std::cerr << "Failed to read data from config file" << std::endl; return false; } #else std::ifstream ifile(config_path.c_str()); if (!ifile.is_open()) { std::cerr << "Failed to open \"" << config_path << "\"" << std::endl; return false; } char ch; while (ifile.get(ch)) { config_data.push_back(ch); } #endif return this->load_config_data(config_data); } const std::string& http_proxy_server_config::get_bind_address() const { return this->get_config_value("bind_address"); } unsigned short http_proxy_server_config::get_listen_port() const { return this->get_config_value("listen_port"); } const std::string& http_proxy_server_config::get_rsa_private_key() const { return this->get_config_value("rsa_private_key"); } unsigned int http_proxy_server_config::get_timeout() const { return this->get_config_value("timeout"); } unsigned int http_proxy_server_config::get_workers() const { return this->get_config_value("workers"); } bool http_proxy_server_config::enable_auth() const { return this->get_config_value("auth"); } http_proxy_server_config& http_proxy_server_config::get_instance() { static http_proxy_server_config instance; return instance; } } // namespace azure_proxy ================================================ FILE: src/http_proxy_server_config.hpp ================================================ /* * http_proxy_server_config.hpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_HTTP_PROXY_SERVER_CONFIG_HPP #define AZURE_HTTP_PROXY_SERVER_CONFIG_HPP #include #include #include #include #include namespace azure_proxy { class http_proxy_server_config { std::map config_map; private: template T get_config_value(const std::string& key) const { assert(!this->config_map.empty()); auto iter = this->config_map.find(key); if (iter == this->config_map.end()) { throw std::invalid_argument("invalid argument"); } return std::any_cast(iter->second); } http_proxy_server_config(); bool load_config_data(const std::string& config_data); public: bool load_config(const std::string& config_path); const std::string& get_bind_address() const; unsigned short get_listen_port() const; const std::string& get_rsa_private_key() const; unsigned int get_timeout() const; unsigned int get_workers() const; bool enable_auth() const; static http_proxy_server_config& get_instance(); }; } // namespace azure_proxy #endif ================================================ FILE: src/http_proxy_server_connection.cpp ================================================ /* * http_proxy_server_connection.cpp: * * Copyright (C) 2013-2024 Light Lin All Rights Reserved. * */ #include #include #include #include #include "authentication.hpp" #include "http_proxy_server_config.hpp" #include "http_proxy_server_connection.hpp" static const std::size_t MAX_REQUEST_HEADER_LENGTH = 10240; static const std::size_t MAX_RESPONSE_HEADER_LENGTH = 10240; namespace azure_proxy { http_proxy_server_connection::http_proxy_server_connection(net::io_context& io_ctx, net::ip::tcp::socket&& proxy_client_socket) : strand(io_ctx.get_executor()), proxy_client_socket(std::move(proxy_client_socket)), origin_server_socket(io_ctx), resolver(io_ctx), timer(io_ctx), rsa_pri(http_proxy_server_config::get_instance().get_rsa_private_key()) { this->connection_context.connection_state = proxy_connection_state::read_cipher_data; } http_proxy_server_connection::~http_proxy_server_connection() { } std::shared_ptr http_proxy_server_connection::create(net::io_context& io_ctx, net::ip::tcp::socket&& client_socket) { return std::shared_ptr(new http_proxy_server_connection(io_ctx, std::move(client_socket))); } void http_proxy_server_connection::start() { this->connection_context.connection_state = proxy_connection_state::read_cipher_data; this->async_read_data_from_proxy_client(1, std::min(this->rsa_pri.modulus_size(), BUFFER_LENGTH)); } void http_proxy_server_connection::async_read_data_from_proxy_client(std::size_t at_least_size, std::size_t at_most_size) { assert(at_least_size <= at_most_size && at_most_size <= BUFFER_LENGTH); auto self(this->shared_from_this()); this->set_timer(); net::async_read(this->proxy_client_socket, net::buffer(&this->upgoing_buffer_read[0], at_most_size), net::transfer_at_least(at_least_size), net::bind_executor(this->strand, [this, self](const std::error_code& error, std::size_t bytes_transferred) { if (this->cancel_timer()) { if (!error) { this->on_proxy_client_data_arrived(bytes_transferred); } else { this->on_error(error); } } }) ); } void http_proxy_server_connection::async_read_data_from_origin_server(bool set_timer, std::size_t at_least_size, std::size_t at_most_size) { auto self(this->shared_from_this()); if (set_timer) { this->set_timer(); } net::async_read(this->origin_server_socket, net::buffer(&this->downgoing_buffer_read[0], at_most_size), net::transfer_at_least(at_least_size), net::bind_executor(this->strand, [this, self](const std::error_code& error, std::size_t bytes_transferred) { if (this->cancel_timer()) { if (!error) { this->on_origin_server_data_arrived(bytes_transferred); } else { this->on_error(error); } } }) ); } void http_proxy_server_connection::async_connect_to_origin_server() { this->connection_context.reconnect_on_error = false; if (this->origin_server_socket.is_open()) { if (this->request_header->method() == "CONNECT") { std::error_code ec; this->origin_server_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->origin_server_socket.close(ec); } } if (this->origin_server_socket.is_open() && this->request_header->host() == this->connection_context.origin_server_name && this->request_header->port() == this->connection_context.origin_server_port) { this->connection_context.reconnect_on_error = true; this->on_connect(); } else { this->connection_context.origin_server_name = this->request_header->host(); this->connection_context.origin_server_port = this->request_header->port(); auto self(this->shared_from_this()); this->connection_context.connection_state = proxy_connection_state::resolve_origin_server_address; this->set_timer(); auto host = this->request_header->host(); if (host.size() >= 2 && host[0] == '[' && host[host.size() - 1] == ']') { host = host.substr(1, host.size() - 2); } this->resolver.async_resolve(host, std::to_string(this->connection_context.origin_server_port), net::bind_executor(this->strand, [this, self](const std::error_code& error, net::ip::tcp::resolver::results_type results) { if (this->cancel_timer()) { if (!error) { this->resolve_results = results; this->on_resolved(results.cbegin()); } else { this->on_error(error); } } }) ); } } void http_proxy_server_connection::async_write_request_header_to_origin_server() { auto request_content_begin = this->request_data.begin() + this->request_data.find("\r\n\r\n") + 4; this->modified_request_data = this->request_header->method(); this->modified_request_data.push_back(' '); this->modified_request_data += this->request_header->path_and_query(); this->modified_request_data += " HTTP/"; this->modified_request_data += this->request_header->http_version(); this->modified_request_data += "\r\n"; this->request_header->erase_header("Proxy-Connection"); this->request_header->erase_header("Proxy-Authorization"); for (const auto& header: this->request_header->get_headers_map()) { this->modified_request_data += std::get<0>(header); this->modified_request_data += ": "; this->modified_request_data += std::get<1>(header); this->modified_request_data += "\r\n"; } this->modified_request_data += "\r\n"; this->modified_request_data.append(request_content_begin, this->request_data.end()); this->connection_context.connection_state = proxy_connection_state::write_http_request_header; this->async_write_data_to_origin_server(this->modified_request_data.data(), 0, this->modified_request_data.size()); } void http_proxy_server_connection::async_write_response_header_to_proxy_client() { auto response_content_begin = this->response_data.begin() + this->response_data.find("\r\n\r\n") + 4; this->modified_response_data = "HTTP/"; this->modified_response_data += this->response_header->http_version(); this->modified_response_data.push_back(' '); this->modified_response_data += std::to_string(this->response_header->status_code()); if (!this->response_header->status_description().empty()) { this->modified_response_data.push_back(' '); this->modified_response_data += this->response_header->status_description(); } this->modified_response_data += "\r\n"; for (const auto& header: this->response_header->get_headers_map()) { this->modified_response_data += std::get<0>(header); this->modified_response_data += ": "; this->modified_response_data += std::get<1>(header); this->modified_response_data += "\r\n"; } this->modified_response_data += "\r\n"; this->modified_response_data.append(response_content_begin, this->response_data.end()); unsigned char temp_buffer[256]; std::size_t blocks = this->modified_response_data.size() / 256; if (this->modified_response_data.size() % 256 != 0) { blocks += 1; } for (std::size_t i = 0; i < blocks; ++i) { std::size_t block_length = 256; if ((i + 1) * 256 > this->modified_response_data.size()) { block_length = this->modified_response_data.size() % 256; } std::copy(reinterpret_cast(&this->modified_response_data[i * 256]), reinterpret_cast(&this->modified_response_data[i * 256 + block_length]), temp_buffer); this->encryptor->encrypt(temp_buffer, reinterpret_cast(&this->modified_response_data[i * 256]), block_length); } this->connection_context.connection_state = proxy_connection_state::write_http_response_header; this->async_write_data_to_proxy_client(this->modified_response_data.data(), 0, this->modified_response_data.size()); } void http_proxy_server_connection::async_write_data_to_origin_server(const char* write_buffer, std::size_t offset, std::size_t size) { auto self(this->shared_from_this()); this->set_timer(); this->origin_server_socket.async_write_some(net::buffer(write_buffer + offset, size), net::bind_executor(this->strand, [this, self, write_buffer, offset, size](const std::error_code& error, std::size_t bytes_transferred) { if (this->cancel_timer()) { if (!error) { this->connection_context.reconnect_on_error = false; if (bytes_transferred < size) { this->async_write_data_to_origin_server(write_buffer, offset + bytes_transferred, size - bytes_transferred); } else { this->on_origin_server_data_written(); } } else { this->on_error(error); } } }) ); } void http_proxy_server_connection::async_write_data_to_proxy_client(const char* write_buffer, std::size_t offset, std::size_t size) { auto self(this->shared_from_this()); this->set_timer(); this->proxy_client_socket.async_write_some(net::buffer(write_buffer + offset, size), net::bind_executor(this->strand, [this, self, write_buffer, offset, size](const std::error_code& error, std::size_t bytes_transferred) { if (this->cancel_timer()) { if (!error) { if (bytes_transferred < size) { this->async_write_data_to_proxy_client(write_buffer, offset + bytes_transferred, size - bytes_transferred); } else { this->on_proxy_client_data_written(); } } else { this->on_error(error); } } }) ); } void http_proxy_server_connection::start_tunnel_transfer() { this->connection_context.connection_state = proxy_connection_state::tunnel_transfer; this->async_read_data_from_proxy_client(); this->async_read_data_from_origin_server(false); } void http_proxy_server_connection::report_error(const std::string& status_code, const std::string& status_description, const std::string& error_message) { this->modified_response_data.clear(); this->modified_response_data += "HTTP/1.1 "; this->modified_response_data += status_code; if (!status_description.empty()) { this->modified_response_data.push_back(' '); this->modified_response_data += status_description; } this->modified_response_data += "\r\n"; this->modified_response_data += "Content-Type: text/html\r\n"; this->modified_response_data += "Server: AzureHttpProxy\r\n"; this->modified_response_data += "Content-Length: "; std::string response_content; response_content = ""; response_content += status_code; response_content += ' '; response_content += status_description; response_content += "

"; response_content += status_code; response_content += ' '; response_content += status_description; response_content += "

"; if (!error_message.empty()) { response_content += "
"; response_content += error_message; response_content += "
"; } response_content += "
"; response_content += "azure http proxy server"; response_content += "
"; this->modified_response_data += std::to_string(response_content.size()); this->modified_response_data += "\r\n"; this->modified_response_data += "Proxy-Connection: close\r\n"; this->modified_response_data += "\r\n"; if (!this->request_header || this->request_header->method() != "HEAD") { this->modified_response_data += response_content; } unsigned char temp_buffer[16]; for (std::size_t i = 0; i * 16 < this->modified_response_data.size(); ++i) { std::size_t block_length = 16; if (this->modified_response_data.size() - i * 16 < 16) { block_length = this->modified_response_data.size() % 16; } this->encryptor->encrypt(reinterpret_cast(&this->modified_response_data[i * 16]), temp_buffer, block_length); std::copy(temp_buffer, temp_buffer + block_length, reinterpret_cast(&this->modified_response_data[i * 16])); } this->connection_context.connection_state = proxy_connection_state::report_error; auto self(this->shared_from_this()); this->async_write_data_to_proxy_client(this->modified_response_data.data(), 0 ,this->modified_response_data.size()); } void http_proxy_server_connection::set_timer() { if (this->timer.expires_after(std::chrono::seconds(http_proxy_server_config::get_instance().get_timeout())) != 0) { assert(false); } auto self(this->shared_from_this()); this->timer.async_wait(net::bind_executor(this->strand, [this, self](const std::error_code& error) { if (error != net::error::operation_aborted) { this->on_timeout(); } })); } bool http_proxy_server_connection::cancel_timer() { std::size_t ret = this->timer.cancel(); assert(ret <= 1); return ret == 1; } void http_proxy_server_connection::on_resolved(net::ip::tcp::resolver::results_type::const_iterator endpoint_iterator) { if (this->origin_server_socket.is_open()) { for (auto iter = endpoint_iterator; iter != this->resolve_results.cend(); ++iter) { if (this->connection_context.origin_server_endpoint == iter->endpoint()) { this->connection_context.reconnect_on_error = true; this->on_connect(); return; } std::error_code ec; this->origin_server_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->origin_server_socket.close(ec); } } this->connection_context.origin_server_endpoint = endpoint_iterator->endpoint(); auto self(this->shared_from_this()); this->connection_context.connection_state = proxy_connection_state::connect_to_origin_server; this->set_timer(); this->origin_server_socket.async_connect(endpoint_iterator->endpoint(), net::bind_executor(this->strand, [this, self, endpoint_iterator](const std::error_code& error) mutable { if (this->cancel_timer()) { if (!error) { this->on_connect(); } else { std::error_code ec; this->origin_server_socket.close(ec); if (++endpoint_iterator != this->resolve_results.cend()) { this->on_resolved(endpoint_iterator); } else { this->on_error(error); } } } }) ); } void http_proxy_server_connection::on_connect() { if (this->request_header->method() == "CONNECT") { const unsigned char response_message[] = "HTTP/1.1 200 Connection Established\r\nConnection: Close\r\n\r\n"; this->modified_response_data.resize(sizeof(response_message) - 1); this->encryptor->encrypt(response_message, const_cast(reinterpret_cast(&this->modified_response_data[0])), this->modified_response_data.size()); this->connection_context.connection_state = proxy_connection_state::report_connection_established; this->async_write_data_to_proxy_client(&this->modified_response_data[0], 0, this->modified_response_data.size()); } else { this->async_write_request_header_to_origin_server(); } } void http_proxy_server_connection::on_proxy_client_data_arrived(std::size_t bytes_transferred) { if (this->connection_context.connection_state == proxy_connection_state::read_cipher_data) { std::copy(this->upgoing_buffer_read.begin(), this->upgoing_buffer_read.begin() + bytes_transferred, std::back_inserter(this->encrypted_cipher_info)); if (this->encrypted_cipher_info.size() < this->rsa_pri.modulus_size()) { this->async_read_data_from_proxy_client(1, std::min(static_cast(this->rsa_pri.modulus_size()) - this->encrypted_cipher_info.size(), BUFFER_LENGTH)); return; } assert(this->encrypted_cipher_info.size() == this->rsa_pri.modulus_size()); std::vector decrypted_cipher_info(this->rsa_pri.modulus_size()); if (86 != this->rsa_pri.decrypt(this->rsa_pri.modulus_size(), this->encrypted_cipher_info.data(), decrypted_cipher_info.data())) { return; } if (decrypted_cipher_info[0] != 'A' || decrypted_cipher_info[1] != 'H' || decrypted_cipher_info[2] != 'P') { return; } unsigned char protocol_version = decrypted_cipher_info[3]; auto need_auth = http_proxy_server_config::get_instance().enable_auth(); if (protocol_version == 0) { if (need_auth || decrypted_cipher_info[4] != 0 || decrypted_cipher_info[6] != 0) { return; } } else if (protocol_version == 1) { if (need_auth) { // 4 auth type // - 0 no authentication // - 1 32 bytes auth_key auto auth_type = decrypted_cipher_info[4]; if (auth_type != 1) { return; } // 6 ~ 37 auth_key std::array auth_key_hash; std::copy(decrypted_cipher_info.cbegin() + 6, decrypted_cipher_info.cbegin() + 38, auth_key_hash.begin()); if (!authentication::get_instance().auth(auth_key_hash)) { return; } } } else { return; } // 5 cipher code // 0x00 aes-128-cfb // 0x01 aes-128-cfb8 // 0x02 aes-128-cfb1 // 0x03 aes-128-ofb // 0x04 aes-128-ctr // 0x05 aes-192-cfb // 0x06 aes-192-cfb8 // 0x07 aes-192-cfb1 // 0x08 aes-192-ofb // 0x09 aes-192-ctr // 0x0A aes-256-cfb // 0x0B aes-256-cfb8 // 0x0C aes-256-cfb1 // 0x0D aes-256-ofb // 0x0E aes-256-ctr unsigned char cipher_code = decrypted_cipher_info[5]; std::size_t ivec_offset = protocol_version == 0 ? 7 : 38; std::size_t cipher_key_offset = protocol_version == 0 ? 23 : 54; if (cipher_code == '\x00' || cipher_code == '\x05' || cipher_code == '\x0A') { // aes-xxx-cfb std::size_t ivec_size = 16; std::size_t key_bits = 256; // aes-256-cfb if (cipher_code == '\x00') { // aes-128-cfb key_bits = 128; } else if (cipher_code == '\x05') { // aes-192-cfb key_bits = 192; } this->encryptor = std::unique_ptr(new aes_cfb128_encryptor(&decrypted_cipher_info[cipher_key_offset], key_bits, &decrypted_cipher_info[ivec_offset])); this->decryptor = std::unique_ptr(new aes_cfb128_decryptor(&decrypted_cipher_info[cipher_key_offset], key_bits, &decrypted_cipher_info[ivec_offset])); } else if (cipher_code == '\x01' || cipher_code == '\x06' || cipher_code == '\x0B') { // ase-xxx-cfb8(deprecated) } else if (cipher_code == '\x02' || cipher_code == '\x07' || cipher_code == '\x0C') { // ase-xxx-cfb1(deprecated) } else if (cipher_code == '\x03' || cipher_code == '\x08' || cipher_code == '\x0D') { // ase-xxx-ofb std::size_t ivec_size = 16; std::size_t key_bits = 256; // aes-256-ofb if (cipher_code == '\x03') { // aes-128-ofb key_bits = 128; } else if (cipher_code == '\x08') { // aes-192-ofb key_bits = 192; } this->encryptor = std::unique_ptr(new aes_ofb128_encryptor(&decrypted_cipher_info[cipher_key_offset], key_bits, &decrypted_cipher_info[ivec_offset])); this->decryptor = std::unique_ptr(new aes_ofb128_decryptor(&decrypted_cipher_info[cipher_key_offset], key_bits, &decrypted_cipher_info[ivec_offset])); } else if (cipher_code == '\x04' || cipher_code == '\x09' || cipher_code == '\x0E') { // ase-xxx-ctr std::size_t ivec_size = 16; std::size_t key_bits = 256; // aes-256-ctr if (cipher_code == '\x04') { // aes-128-ctr key_bits = 128; } else if (cipher_code == '\x09') { // aes-192-ctr key_bits = 192; } std::vector ivec(ivec_size, 0); this->encryptor = std::unique_ptr(new aes_ctr128_encryptor(&decrypted_cipher_info[cipher_key_offset], key_bits, ivec.data())); this->decryptor = std::unique_ptr(new aes_ctr128_decryptor(&decrypted_cipher_info[cipher_key_offset], key_bits, ivec.data())); } if (this->encryptor == nullptr || this->decryptor == nullptr) { return; } this->connection_context.connection_state = proxy_connection_state::read_http_request_header; this->async_read_data_from_proxy_client(); return; } assert(this->encryptor != nullptr && this->decryptor != nullptr); this->decryptor->decrypt(reinterpret_cast(&this->upgoing_buffer_read[0]), reinterpret_cast(&this->upgoing_buffer_write[0]), bytes_transferred); if (this->connection_context.connection_state == proxy_connection_state::read_http_request_header) { const auto& decripted_data_buffer = this->upgoing_buffer_write; this->request_data.append(decripted_data_buffer.begin(), decripted_data_buffer.begin() + bytes_transferred); auto double_crlf_pos = this->request_data.find("\r\n\r\n"); if (double_crlf_pos == std::string::npos) { if (this->request_data.size() < MAX_REQUEST_HEADER_LENGTH) { this->async_read_data_from_proxy_client(); } else { this->report_error("400", "Bad Request", "Request header too long"); } return; } this->request_header = http_header_parser::parse_request_header(this->request_data.begin(), this->request_data.begin() + double_crlf_pos + 4); if (!this->request_header) { this->report_error("400", "Bad Request", "Failed to parse the http request header"); return; } if (this->request_header->method() != "GET" // && this->request_header->method() != "OPTIONS" && this->request_header->method() != "HEAD" && this->request_header->method() != "POST" && this->request_header->method() != "PUT" && this->request_header->method() != "DELETE" // && this->request_header->method() != "TRACE" && this->request_header->method() != "CONNECT") { this->report_error("405", "Method Not Allowed", std::string()); return; } if (this->request_header->http_version() != "1.1" && this->request_header->http_version() != "1.0") { this->report_error("505", "HTTP Version Not Supported", std::string()); return; } if (this->request_header->method() == "CONNECT") { this->async_connect_to_origin_server(); return; } else { if (this->request_header->scheme() != "http") { this->report_error("400", "Bad Request", "Unsupported scheme"); return; } auto proxy_connection_value = this->request_header->get_header_value("Proxy-Connection"); auto connection_value = this->request_header->get_header_value("Connection"); auto string_to_lower_case = [](std::string& str) { for (auto iter = str.begin(); iter != str.end(); ++iter) { *iter = std::tolower(static_cast(*iter)); } }; if (proxy_connection_value) { string_to_lower_case(*proxy_connection_value); if (this->request_header->http_version() == "1.1") { this->read_request_context.is_proxy_client_keep_alive = true; if (*proxy_connection_value == "close") { this->read_request_context.is_proxy_client_keep_alive = false; } } else { assert(this->request_header->http_version() == "1.0"); this->read_request_context.is_proxy_client_keep_alive = false; if (*proxy_connection_value == "keep-alive") { this->read_request_context.is_proxy_client_keep_alive = true; } } } else { if (this->request_header->http_version() == "1.1") { this->read_request_context.is_proxy_client_keep_alive = true; } else { this->read_request_context.is_proxy_client_keep_alive = false; } if (connection_value) { string_to_lower_case(*connection_value); if (this->request_header->http_version() == "1.1" && *connection_value == "close") { this->read_request_context.is_proxy_client_keep_alive = false; } else if (this->request_header->http_version() == "1.0" && *connection_value == "keep-alive") { this->read_request_context.is_proxy_client_keep_alive = true; } } } this->read_request_context.content_length = std::optional(); this->read_request_context.content_length_has_read = 0; this->read_request_context.chunk_checker = std::optional(); if (this->request_header->method() == "GET" || this->request_header->method() == "HEAD" || this->request_header->method() == "DELETE") { this->read_request_context.content_length = std::optional(0); } else if (this->request_header->method() == "POST" || this->request_header->method() == "PUT") { auto content_length_value = this->request_header->get_header_value("Content-Length"); auto transfer_encoding_value = this->request_header->get_header_value("Transfer-Encoding"); if (content_length_value) { try { this->read_request_context.content_length = std::optional(std::stoull(*content_length_value)); } catch (const std::exception&) { this->report_error("400", "Bad Request", "Invalid Content-Length in request"); return; } this->read_request_context.content_length_has_read = this->request_data.size() - (double_crlf_pos + 4); } else if (transfer_encoding_value) { string_to_lower_case(*transfer_encoding_value); if (*transfer_encoding_value == "chunked") { if (!this->read_request_context.chunk_checker->check(this->request_data.begin() + double_crlf_pos + 4, this->request_data.end())) { this->report_error("400", "Bad Request", "Failed to check chunked response"); return; } return; } else { this->report_error("400", "Bad Request", "Unsupported Transfer-Encoding"); return; } } else { this->report_error("411", "Length Required", std::string()); return; } } else { assert(false); return; } } this->async_connect_to_origin_server(); } else if (this->connection_context.connection_state == proxy_connection_state::read_http_request_content) { if (this->read_request_context.content_length) { this->read_request_context.content_length_has_read += bytes_transferred; } else { assert(this->read_request_context.chunk_checker); if (!this->read_request_context.chunk_checker->check(this->upgoing_buffer_write.begin(), this->upgoing_buffer_write.begin() + bytes_transferred)) { return; } } this->connection_context.connection_state = proxy_connection_state::write_http_request_content; this->async_write_data_to_origin_server(this->upgoing_buffer_write.data(), 0, bytes_transferred); } else if (this->connection_context.connection_state == proxy_connection_state::tunnel_transfer) { this->async_write_data_to_origin_server(this->upgoing_buffer_write.data(), 0, bytes_transferred); } } void http_proxy_server_connection::on_origin_server_data_arrived(std::size_t bytes_transferred) { if (this->connection_context.connection_state == proxy_connection_state::read_http_response_header) { this->response_data.append(this->downgoing_buffer_read.begin(), this->downgoing_buffer_read.begin() + bytes_transferred); auto double_crlf_pos = this->response_data.find("\r\n\r\n"); if (double_crlf_pos == std::string::npos) { if (this->response_data.size() < MAX_RESPONSE_HEADER_LENGTH) { this->async_read_data_from_origin_server(); } else { this->report_error("502", "Bad Gateway", "Response header too long"); } return; } this->response_header = http_header_parser::parse_response_header(this->response_data.begin(), this->response_data.begin() + double_crlf_pos + 4); if (!this->response_header) { this->report_error("502", "Bad Gateway", "Failed to parse response header"); return; } if (this->response_header->http_version() != "1.1" && this->response_header->http_version() != "1.0") { this->report_error("502", "Bad Gateway", "HTTP version not supported"); return; } if (this->response_header->status_code() < 100 || this->response_header->status_code() > 700) { this->report_error("502", "Bad Gateway", "Unexpected status code"); return; } this->read_response_context.content_length = std::optional(); this->read_response_context.content_length_has_read = 0; this->read_response_context.is_origin_server_keep_alive = false; this->read_response_context.chunk_checker = std::optional(); auto connection_value = this->response_header->get_header_value("Connection"); if (this->response_header->http_version() == "1.1") { this->read_response_context.is_origin_server_keep_alive = true; } else { this->read_response_context.is_origin_server_keep_alive = false; } auto string_to_lower_case = [](std::string& str) { for (auto iter = str.begin(); iter != str.end(); ++iter) { *iter = std::tolower(static_cast(*iter)); } }; if (connection_value) { string_to_lower_case(*connection_value); if (*connection_value == "close") { this->read_response_context.is_origin_server_keep_alive = false; } else if (*connection_value == "keep-alive") { this->read_response_context.is_origin_server_keep_alive = true; } else { this->report_error("502", "Bad Gateway", std::string()); return; } } if (this->request_header->method() == "HEAD") { this->read_response_context.content_length = std::optional(0); } else if (this->response_header->status_code() == 204 || this->response_header->status_code() == 304) { // 204 No Content // 304 Not Modified this->read_response_context.content_length = std::optional(0); } else { auto content_length_value = this->response_header->get_header_value("Content-Length"); auto transfer_encoding_value = this->response_header->get_header_value("Transfer-Encoding"); if (content_length_value) { try { this->read_response_context.content_length = std::optional(std::stoull(*content_length_value)); } catch(const std::exception&) { this->report_error("502", "Bad Gateway", "Invalid Content-Length in response"); return; } this->read_response_context.content_length_has_read = this->response_data.size() - (double_crlf_pos + 4); } else if (transfer_encoding_value) { string_to_lower_case(*transfer_encoding_value); if (*transfer_encoding_value == "chunked") { this->read_response_context.chunk_checker = std::optional(http_chunk_checker()); if (!this->read_response_context.chunk_checker->check(this->response_data.begin() + double_crlf_pos + 4, this->response_data.end())) { this->report_error("502", "Bad Gateway", "Failed to check chunked response"); return; } } else { this->report_error("502", "Bad Gateway", "Unsupported Transfer-Encoding"); return; } } else if (this->read_response_context.is_origin_server_keep_alive) { this->report_error("502", "Bad Gateway", "Miss response length info"); return; } } this->async_write_response_header_to_proxy_client(); } else if (this->connection_context.connection_state == proxy_connection_state::read_http_response_content) { if (this->read_response_context.content_length) { this->read_response_context.content_length_has_read += bytes_transferred; } else if (this->read_response_context.chunk_checker) { if (!this->read_response_context.chunk_checker->check(this->downgoing_buffer_read.begin(), this->downgoing_buffer_read.begin() + bytes_transferred)) { return; } } this->connection_context.connection_state = proxy_connection_state::write_http_response_content; this->encryptor->encrypt(reinterpret_cast(&this->downgoing_buffer_read[0]), reinterpret_cast(&this->downgoing_buffer_write[0]), bytes_transferred); this->async_write_data_to_proxy_client(this->downgoing_buffer_write.data(), 0, bytes_transferred); } else if (this->connection_context.connection_state == proxy_connection_state::tunnel_transfer) { this->encryptor->encrypt(reinterpret_cast(&this->downgoing_buffer_read[0]), reinterpret_cast(&this->downgoing_buffer_write[0]), bytes_transferred); this->async_write_data_to_proxy_client(this->downgoing_buffer_write.data(), 0, bytes_transferred); } } void http_proxy_server_connection::on_proxy_client_data_written() { if (this->connection_context.connection_state == proxy_connection_state::tunnel_transfer) { this->async_read_data_from_origin_server(); } else if (this->connection_context.connection_state == proxy_connection_state::write_http_response_header || this->connection_context.connection_state == proxy_connection_state::write_http_response_content) { if ((this->read_response_context.content_length && this->read_response_context.content_length_has_read >= *this->read_response_context.content_length) || (this->read_response_context.chunk_checker && this->read_response_context.chunk_checker->is_complete())) { std::error_code ec; if (!this->read_response_context.is_origin_server_keep_alive) { this->origin_server_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->origin_server_socket.close(ec); } if (this->read_request_context.is_proxy_client_keep_alive) { this->request_data.clear(); this->response_data.clear(); this->request_header = std::optional(); this->response_header = std::optional(); this->read_request_context.content_length = std::optional(); this->read_request_context.chunk_checker = std::optional(); this->read_response_context.content_length = std::optional(); this->read_response_context.chunk_checker = std::optional(); this->connection_context.connection_state = proxy_connection_state::read_http_request_header; this->async_read_data_from_proxy_client(); } else { this->proxy_client_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->proxy_client_socket.close(ec); } } else { this->connection_context.connection_state = proxy_connection_state::read_http_response_content; this->async_read_data_from_origin_server(); } } else if (this->connection_context.connection_state == proxy_connection_state::report_connection_established) { this->start_tunnel_transfer(); } else if (this->connection_context.connection_state == proxy_connection_state::report_error) { std::error_code ec; if (this->origin_server_socket.is_open()) { this->origin_server_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->origin_server_socket.close(ec); } if (this->proxy_client_socket.is_open()) { this->proxy_client_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->proxy_client_socket.close(ec); } } } void http_proxy_server_connection::on_origin_server_data_written() { if (this->connection_context.connection_state == proxy_connection_state::tunnel_transfer) { this->async_read_data_from_proxy_client(); } else if (this->connection_context.connection_state == proxy_connection_state::write_http_request_header || this->connection_context.connection_state == proxy_connection_state::write_http_request_content) { if (this->read_request_context.content_length) { if (this->read_request_context.content_length_has_read < *this->read_request_context.content_length) { this->connection_context.connection_state = proxy_connection_state::read_http_request_content; this->async_read_data_from_proxy_client(); } else { this->connection_context.connection_state = proxy_connection_state::read_http_response_header; this->async_read_data_from_origin_server(); } } else { assert(this->read_request_context.chunk_checker); if (!this->read_request_context.chunk_checker->is_complete()) { this->connection_context.connection_state = proxy_connection_state::read_http_request_content; this->async_read_data_from_proxy_client(); } else { this->connection_context.connection_state = proxy_connection_state::read_http_response_header; this->async_read_data_from_origin_server(); } } } } void http_proxy_server_connection::on_error(const std::error_code& error) { if (this->connection_context.connection_state == proxy_connection_state::resolve_origin_server_address) { this->report_error("504", "Gateway Timeout", "Failed to resolve the hostname"); } else if (this->connection_context.connection_state == proxy_connection_state::connect_to_origin_server) { this->report_error("502", "Bad Gateway", "Failed to connect to origin server"); } else if (this->connection_context.connection_state == proxy_connection_state::write_http_request_header && this->connection_context.reconnect_on_error) { std::error_code ec; this->origin_server_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->origin_server_socket.close(ec); this->async_connect_to_origin_server(); } else { std::error_code ec; if (this->origin_server_socket.is_open()) { this->origin_server_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->origin_server_socket.close(ec); } if (this->proxy_client_socket.is_open()) { this->proxy_client_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->proxy_client_socket.close(ec); } } } void http_proxy_server_connection::on_timeout() { std::error_code ec; if (this->origin_server_socket.is_open()) { this->origin_server_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->origin_server_socket.close(ec); } if (this->proxy_client_socket.is_open()) { this->proxy_client_socket.shutdown(net::ip::tcp::socket::shutdown_both, ec); this->proxy_client_socket.close(ec); } } } // namespace azure_proxy ================================================ FILE: src/http_proxy_server_connection.hpp ================================================ /* * http_proxy_server_connection.hpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_HTTP_PROXY_SERVER_CONNECTION_HPP #define AZURE_HTTP_PROXY_SERVER_CONNECTION_HPP #include #include #include #include #include "encrypt.hpp" #include "http_header_parser.hpp" #include "http_proxy_server_connection_context.hpp" namespace net = std::experimental::net; namespace azure_proxy { const std::size_t BUFFER_LENGTH = 2048; class http_proxy_server_connection : public std::enable_shared_from_this { net::strand strand; net::ip::tcp::socket proxy_client_socket; net::ip::tcp::socket origin_server_socket; net::ip::tcp::resolver resolver; net::ip::tcp::resolver::results_type resolve_results; net::basic_waitable_timer timer; std::array upgoing_buffer_read; std::array upgoing_buffer_write; std::array downgoing_buffer_read; std::array downgoing_buffer_write; rsa rsa_pri; std::vector encrypted_cipher_info; std::unique_ptr encryptor; std::unique_ptr decryptor; std::string request_data; std::string modified_request_data; std::string response_data; std::string modified_response_data; std::optional request_header; std::optional response_header; http_proxy_server_connection_context connection_context; http_proxy_server_connection_read_request_context read_request_context; http_proxy_server_connection_read_response_context read_response_context; private: http_proxy_server_connection(net::io_context& io_ctx, net::ip::tcp::socket&& proxy_client_socket); public: ~http_proxy_server_connection(); static std::shared_ptr create(net::io_context& io_ctx, net::ip::tcp::socket&& client_socket); void start(); private: void async_read_data_from_proxy_client(std::size_t at_least_size = 1, std::size_t at_most_size = BUFFER_LENGTH); void async_read_data_from_origin_server(bool set_timer = true, std::size_t at_least_size = 1, std::size_t at_most_size = BUFFER_LENGTH); void async_connect_to_origin_server(); void async_write_request_header_to_origin_server(); void async_write_response_header_to_proxy_client(); void async_write_data_to_origin_server(const char* write_buffer, std::size_t offset, std::size_t size); void async_write_data_to_proxy_client(const char* write_buffer, std::size_t offset, std::size_t size); void start_tunnel_transfer(); void report_error(const std::string& status_code, const std::string& status_description, const std::string& error_message); void set_timer(); bool cancel_timer(); void on_resolved(net::ip::tcp::resolver::results_type::const_iterator endpoint_iterator); void on_connect(); void on_proxy_client_data_arrived(std::size_t bytes_transferred); void on_origin_server_data_arrived(std::size_t bytes_transferred); void on_proxy_client_data_written(); void on_origin_server_data_written(); void on_error(const std::error_code& error); void on_timeout(); }; } // namespace azure_proxy #endif ================================================ FILE: src/http_proxy_server_connection_context.hpp ================================================ /* * http_proxy_server_connection_context.hpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_HTTP_PROXY_SERVER_CONNECTION_CONTEXT_HPP #define AZURE_HTTP_PROXY_SERVER_CONNECTION_CONTEXT_HPP #include #include #include #include "http_chunk_checker.hpp" namespace net = std::experimental::net; namespace azure_proxy { enum class proxy_connection_state { read_cipher_data, resolve_origin_server_address, connect_to_origin_server, tunnel_transfer, read_http_request_header, write_http_request_header, read_http_request_content, write_http_request_content, read_http_response_header, write_http_response_header, read_http_response_content, write_http_response_content, report_connection_established, report_error }; struct http_proxy_server_connection_context { proxy_connection_state connection_state; bool reconnect_on_error; std::string origin_server_name; unsigned short origin_server_port; std::optional origin_server_endpoint; }; struct http_proxy_server_connection_read_request_context { bool is_proxy_client_keep_alive; std::optional content_length; std::uint64_t content_length_has_read; std::optional chunk_checker; }; struct http_proxy_server_connection_read_response_context { bool is_origin_server_keep_alive; std::optional content_length; std::uint64_t content_length_has_read; std::optional chunk_checker; }; } // namespace azure_proxy #endif ================================================ FILE: src/http_proxy_server_main.cpp ================================================ /* * http_proxy_server_main.cpp: * * Copyright (C) 2013-2023 Light Lin All Rights Reserved. * */ #include #include #include #include #include #include #include "http_proxy_server_config.hpp" #include "http_proxy_server.hpp" #include "version.hpp" #ifdef _WIN32 #include #include #endif namespace net = std::experimental::net; struct ServerArgs { std::string config_file = "server.json"; }; void print_usage() { #ifdef _WIN32 const char *prog = "ahps.exe"; #else const char *prog = "ahps"; #endif std::cout << "Usage: " << prog << " [options]\n\n" << "options:\n" << " -h, --help Show this help message and exit\n" << " -v, --version Print the program version and exit\n" << " -c, --config PATH Configuration file path (default: server.json)\n"; } static ServerArgs parse_args(const std::vector& argv) { std::string arg; bool invalid_param = false; ServerArgs args; for (std::size_t i = 1; i < argv.size(); ++i) { arg = argv[i]; if (arg == "-h" || arg == "--help") { print_usage(); std::exit(EXIT_SUCCESS); } else if (arg == "-v" || arg == "--version") { std::cout << "Version: " << AHP_VERSION_STRING << std::endl; std::exit(EXIT_SUCCESS); } else if (arg == "-c" || arg == "--config") { if (++i >= argv.size()) { invalid_param = true; break; } args.config_file = argv[i]; } else { std::cerr << "Unknown argument: " << arg << std::endl; print_usage(); std::exit(EXIT_FAILURE); } } if (invalid_param) { std::cerr << "Invalid parameter for argument: " << arg << std::endl; std::exit(EXIT_FAILURE); } return args; } static ServerArgs parse_args(int argc, char** argv) { std::vector argv_vec; argv_vec.reserve(argc); #ifdef _WIN32 LPWSTR *wargs = CommandLineToArgvW(GetCommandLineW(), &argc); if (wargs == nullptr) { std::cerr << "Failed to retrieve command line arguments" << std::endl; std::exit(EXIT_FAILURE); } std::wstring_convert> converter; for (std::size_t i = 0; i < argc; ++i) { argv_vec.emplace_back(converter.to_bytes(wargs[i])); } LocalFree(wargs); #else for (std::size_t i = 0; i < argc; ++i) { argv_vec.emplace_back(argv[i]); } #endif return parse_args(argv_vec); } std::weak_ptr wp_io_ctx; static void signal_handler(int signal) { auto io_ctx = wp_io_ctx.lock(); if (io_ctx) { io_ctx->stop(); } } int main(int argc, char** argv) { using namespace azure_proxy; auto args = parse_args(argc, argv); try { auto& config = http_proxy_server_config::get_instance(); if (config.load_config(args.config_file)) { std::cout << "AHP server version " << AHP_VERSION_STRING << std::endl; std::cout << "bind address: " << config.get_bind_address() << ':' << config.get_listen_port() << std::endl; auto io_ctx = std::make_shared(); wp_io_ctx = io_ctx; http_proxy_server server(*io_ctx); std::signal(SIGINT, signal_handler); std::signal(SIGTERM, signal_handler); server.run(); } } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } return 0; } ================================================ FILE: src/key_generator.hpp ================================================ /* * key_gennerator.hpp: * * Copyright (C) 2014-2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_KEY_GENERATOR_HPP #define AZURE_KEY_GENERATOR_HPP #include #include #include #include #include namespace azure_proxy { class key_generator { std::mt19937 gen; std::mutex mtx; key_generator() { std::uint64_t seed = reinterpret_cast(std::unique_ptr(new int(0)).get()) ^ static_cast(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count()); this->gen.seed(seed); } public: void generate(unsigned char* out, std::size_t length) { assert(out); std::uniform_int_distribution dis(0, 255); std::lock_guard lck(this->mtx); for (std::size_t i = 0; i < length; ++i) { out[i] = static_cast(dis(this->gen)); } } static key_generator& get_instance() { static key_generator instance; return instance; } }; } #endif ================================================ FILE: src/version.hpp ================================================ /* * version.hpp: * * Copyright (C) 2023 Light Lin All Rights Reserved. * */ #ifndef AZURE_VERSION_HPP #define AZURE_VERSION_HPP #define AHP_MAJOR_VERSION 1 #define AHP_MINOR_VERSION 2 #define AHP_PATCH_VERSION 0 #define AHP_VERSION_TO_STR_(x) #x #define AHP_VERSION_TO_STR(x) AHP_VERSION_TO_STR_(x) #define AHP_VERSION_DOT AHP_VERSION_TO_STR_(.) #if AHP_PATCH_VERSION > 0 #define AHP_VERSION_STRING AHP_VERSION_TO_STR(AHP_MAJOR_VERSION) AHP_VERSION_DOT AHP_VERSION_TO_STR(AHP_MINOR_VERSION) AHP_VERSION_DOT AHP_VERSION_TO_STR(AHP_PATCH_VERSION) #else #define AHP_VERSION_STRING AHP_VERSION_TO_STR(AHP_MAJOR_VERSION) AHP_VERSION_DOT AHP_VERSION_TO_STR(AHP_MINOR_VERSION) #endif #endif // AZURE_VERSION_HPP