├── logo ├── icon.ico ├── icon.png ├── iconx1024.png └── icon.svg ├── .gitmodules ├── include └── pegasocks │ ├── codec │ ├── trojan.h │ ├── shadowsocks.h │ ├── vmess.h │ ├── codec.h │ └── websocket.h │ ├── ssl.h │ ├── mpsc.h │ ├── pegas.h │ ├── acl.h │ ├── server │ ├── manager.h │ ├── control.h │ ├── metrics.h │ ├── local.h │ └── helper.h │ ├── applet.h │ ├── defs.h │ ├── utils.h │ ├── session │ ├── inbound.h │ ├── session.h │ ├── udp.h │ └── outbound.h │ ├── log.h │ ├── dns.h │ ├── config.h │ └── crypto.h ├── 3rd-party ├── longlong.h ├── tray │ └── LICENSE ├── sha3.h ├── hash_32a.c ├── sha3.c └── fnv.h ├── CHANGELOG.md ├── cmake ├── FindOpenSSLx.cmake ├── FindJeMalloc.cmake ├── FindPCRE.cmake ├── FindLibevent2.cmake └── FindMbedTLS.cmake ├── src ├── mpsc.c ├── main.c ├── utils.c ├── server │ ├── manager.c │ ├── helper.c │ ├── local.c │ └── control.c ├── codec │ ├── trojan.c │ ├── websocket.c │ └── shadowsocks.c ├── session │ └── session.c ├── ssl │ ├── mbedtls.c │ └── openssl.c ├── log.c ├── applet.c ├── crypto │ └── openssl.c ├── acl.c └── pegas.c ├── distribution └── macos │ ├── AppIcon.iconset │ └── Contents.json │ └── Info.plist.in ├── LICENSE ├── WINDOWS_BUILD.md ├── test └── CMakeLists.txt ├── acl └── genacl.py ├── README_zh.md ├── .cirrus.yml ├── README.md └── doc └── pegas.1.asciidoc /logo/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chux0519/pegasocks/HEAD/logo/icon.ico -------------------------------------------------------------------------------- /logo/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chux0519/pegasocks/HEAD/logo/icon.png -------------------------------------------------------------------------------- /logo/iconx1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chux0519/pegasocks/HEAD/logo/iconx1024.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rd-party/parson"] 2 | path = 3rd-party/parson 3 | url = https://github.com/kgabis/parson.git 4 | [submodule "3rd-party/libcork"] 5 | path = 3rd-party/libcork 6 | url = https://github.com/shadowsocks/libcork 7 | [submodule "3rd-party/ipset"] 8 | path = 3rd-party/ipset 9 | url = https://github.com/shadowsocks/ipset 10 | -------------------------------------------------------------------------------- /include/pegasocks/codec/trojan.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_CODEC_TROJAN_H 2 | #define _PGS_CODEC_TROJAN_H 3 | 4 | #include "session/session.h" 5 | 6 | bool trojan_write_remote(pgs_session_t *session, const uint8_t *msg, size_t len, 7 | size_t *olen); 8 | bool trojan_write_local(pgs_session_t *session, const uint8_t *msg, size_t len, 9 | size_t *olen); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /include/pegasocks/codec/shadowsocks.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_CODEC_SHADOWSOCKS_H 2 | #define _PGS_CODEC_SHADOWSOCKS_H 3 | 4 | #include "session/session.h" 5 | 6 | bool shadowsocks_write_remote(pgs_session_t *session, const uint8_t *msg, 7 | size_t len, size_t *olen); 8 | bool shadowsocks_write_local(pgs_session_t *session, const uint8_t *msg, 9 | size_t len, size_t *olen, size_t *clen); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /3rd-party/longlong.h: -------------------------------------------------------------------------------- 1 | /* 2 | * DO NOT EDIT -- generated by the Makefile 3 | */ 4 | 5 | #if !defined(__LONGLONG_H__) 6 | #define __LONGLONG_H__ 7 | 8 | /* do we have/want to use a long long type? */ 9 | #define HAVE_64BIT_LONG_LONG /* yes */ 10 | 11 | /* 12 | * NO64BIT_LONG_LONG undef HAVE_64BIT_LONG_LONG 13 | */ 14 | #if defined(NO64BIT_LONG_LONG) 15 | #undef HAVE_64BIT_LONG_LONG 16 | #endif /* NO64BIT_LONG_LONG */ 17 | 18 | #endif /* !__LONGLONG_H__ */ 19 | -------------------------------------------------------------------------------- /include/pegasocks/ssl.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_SSL_H 2 | #define _PGS_SSL_H 3 | 4 | #include "config.h" 5 | 6 | #include 7 | 8 | struct pgs_ssl_ctx_s; 9 | 10 | typedef struct pgs_ssl_ctx_s pgs_ssl_ctx_t; 11 | 12 | pgs_ssl_ctx_t *pgs_ssl_ctx_new(pgs_config_t *config); 13 | void pgs_ssl_ctx_free(pgs_ssl_ctx_t *ctx); 14 | 15 | int pgs_session_outbound_ssl_bev_init(struct bufferevent **bev, int fd, 16 | struct event_base *base, 17 | pgs_ssl_ctx_t *ssl_ctx, const char *sni); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /include/pegasocks/codec/vmess.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_CODEC_VMESS_H 2 | #define _PGS_CODEC_VMESS_H 3 | 4 | #include "session/session.h" 5 | 6 | typedef void (*pgs_session_write_fn)(pgs_session_t *, uint8_t *, size_t); 7 | 8 | typedef struct pgs_vmess_resp_s { 9 | uint8_t v; 10 | uint8_t opt; 11 | uint8_t cmd; 12 | uint8_t m; 13 | } pgs_vmess_resp_t; 14 | 15 | bool vmess_write_remote(pgs_session_t *session, const uint8_t *data, 16 | size_t data_len, size_t *olen); 17 | bool vmess_write_local(pgs_session_t *session, const uint8_t *data, 18 | size_t data_len, size_t *olen); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /include/pegasocks/mpsc.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_MPSC_H 2 | #define _PGS_MPSC_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct pgs_mpsc_s pgs_mpsc_t; 9 | 10 | struct pgs_mpsc_s { 11 | _Atomic long count; 12 | _Atomic long in_pos; 13 | long out_pos; // using from one thread(consumer), so thread safe 14 | long mask; 15 | void *_Atomic *slots; 16 | }; 17 | 18 | pgs_mpsc_t *pgs_mpsc_new(long size /* should be power of 2 */); 19 | 20 | void pgs_mpsc_free(pgs_mpsc_t *mpsc); 21 | 22 | bool pgs_mpsc_send(pgs_mpsc_t *mpsc, void *data); 23 | 24 | void *pgs_mpsc_recv(pgs_mpsc_t *mpsc); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /include/pegasocks/pegas.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_PEGAS_H 2 | #define _PGS_PEGAS_H 3 | 4 | #include "stdbool.h" 5 | 6 | #ifndef PGS_VERSION 7 | #define PGS_VERSION "v0.0.0-develop" 8 | #endif 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | bool pgs_start(const char *config, const char *acl, int threads, 15 | void (*shutdown)()); 16 | void pgs_stop(); 17 | 18 | void pgs_get_version(char *version); 19 | 20 | /* 21 | * pgs_get_servers will encode metrics as json string to `out` 22 | * and set the length of it to `olen` 23 | * */ 24 | void pgs_get_servers(char *out, int max_len, int *olen); 25 | bool pgs_set_server(int idx); 26 | 27 | #ifdef __cplusplus 28 | } 29 | #endif 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /include/pegasocks/acl.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_ACL_H 2 | #define _PGS_ACL_H 3 | 4 | #include 5 | 6 | struct pgs_acl_s; 7 | typedef struct pgs_acl_s pgs_acl_t; 8 | 9 | struct pgs_acl_rule_s; 10 | typedef struct pgs_acl_rule_s pgs_acl_rule_t; 11 | 12 | typedef enum { 13 | PROXY_ALL_BYPASS_LIST, 14 | BYPASS_ALL_PROXY_LIST, 15 | } pgs_acl_mode; 16 | 17 | pgs_acl_t *pgs_acl_new(const char *path); 18 | 19 | void pgs_acl_free(pgs_acl_t *acl); 20 | 21 | pgs_acl_rule_t *pgs_acl_rule_new(const char *raw); 22 | 23 | void pgs_acl_rule_free(pgs_acl_rule_t *rule); 24 | 25 | bool pgs_acl_match_host_bypass(pgs_acl_t *acl, const char *host); 26 | bool pgs_acl_match_host_proxy(pgs_acl_t *acl, const char *host); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 20201218 2 | 3 | - remove bundle script, use cmake to generate app bundle on OSX. 4 | - change default configuration from `~/.pegasrc` to `~/.config/.pegasrc` on OSX 5 | 6 | ## 20210809 7 | 8 | - support UDP 9 | - ssl config changed, sni is supported now, by specifying `ssl.sni` 10 | - remove deprecated methods of openssl and json-c 11 | 12 | ## 20210921 13 | 14 | - support ACL 15 | - support mbedtls 16 | - support jemalloc 17 | - use git submodule to manage dependencies 18 | 19 | ## 20210927 20 | 21 | - implemented shadowsocks protocol (UDP and plugin features will be implemented later) 22 | - support more ciphers, currently, `aes-128-cfb`, `aes-128-gcm`, `aes-256-gcm` and `chacha20-poly1305` are supported 23 | - clean code and file structure change (for better maintenance) 24 | - ci updates for mbedtls build test 25 | - bugfixes 26 | -------------------------------------------------------------------------------- /cmake/FindOpenSSLx.cmake: -------------------------------------------------------------------------------- 1 | if(DEFINED OpenSSLx_ROOT) 2 | set(OPENSSL_ROOT_DIR ${OpenSSLx_ROOT}) 3 | endif() 4 | if (APPLE) 5 | # This is a bug in CMake that causes it to prefer the system version over 6 | # the one in the specified ROOT folder. 7 | if(NOT DEFINED OPENSSL_ROOT_DIR) 8 | execute_process ( 9 | COMMAND sh -c "ls /usr/local/Cellar/openssl@1.1/ | sort -r | grep -m1 1.1" 10 | OUTPUT_VARIABLE OPENSSL_VERSION 11 | ) 12 | string(REGEX REPLACE "\n$" "" OPENSSL_VERSION "${OPENSSL_VERSION}") 13 | set(OPENSSL_ROOT_DIR ${OPENSSL_ROOT_DIR} /usr/local/Cellar/openssl@1.1/${OPENSSL_VERSION}/) 14 | endif() 15 | MESSAGE(STATUS "Found openssl library root: ${OPENSSL_ROOT_DIR}") 16 | endif() 17 | find_package(OpenSSL 1.1.0 REQUIRED) 18 | MESSAGE(STATUS "Found openssl libraries: ${OPENSSL_LIBRARIES}") 19 | -------------------------------------------------------------------------------- /include/pegasocks/codec/codec.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_CODEC_H 2 | #define _PGS_CODEC_H 3 | 4 | #include "defs.h" 5 | #include "websocket.h" 6 | #include "vmess.h" 7 | #include "trojan.h" 8 | #include "shadowsocks.h" 9 | 10 | #ifndef htonll 11 | #define htonll(x) \ 12 | ((1 == htonl(1)) ? \ 13 | (x) : \ 14 | ((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32)) 15 | #endif 16 | 17 | #ifndef ntohll 18 | #define ntohll(x) htonll(x) 19 | #endif 20 | 21 | // static helper functions 22 | static inline int pgs_get_addr_len(const uint8_t *data) 23 | { 24 | switch (data[0] /*atype*/) { 25 | case 0x01: 26 | // IPv4 27 | return 4; 28 | case 0x03: 29 | return 1 + data[1]; 30 | case 0x04: 31 | // IPv6 32 | return 16; 33 | default: 34 | break; 35 | } 36 | return 0; 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /include/pegasocks/server/manager.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_SERVER_MANAGER_H 2 | #define _PGS_SERVER_MANAGER_H 3 | 4 | #include 5 | 6 | #include "defs.h" 7 | #include "config.h" 8 | 9 | #define MAX_SESSION_STATS_SIZE 16 10 | 11 | typedef struct pgs_server_stats_s { 12 | double connect_delay; 13 | double g204_delay; 14 | } pgs_server_stats_t; 15 | 16 | typedef struct pgs_server_manager_s { 17 | pgs_server_stats_t *server_stats; 18 | pgs_server_config_t *server_configs; 19 | int server_len; 20 | int cur_server_index; 21 | } pgs_server_manager_t; 22 | 23 | pgs_server_manager_t * 24 | pgs_server_manager_new(pgs_server_config_t *server_configs, int server_len); 25 | void pgs_server_manager_free(pgs_server_manager_t *sm); 26 | 27 | pgs_server_config_t *pgs_server_manager_get_config(pgs_server_manager_t *sm); 28 | 29 | void pgs_sm_get_servers(pgs_server_manager_t *SM, char *out, int max_len, 30 | int *olen); 31 | bool pgs_sm_set_server(pgs_server_manager_t *SM, int idx); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /include/pegasocks/server/control.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_CONTROL_H 2 | #define _PGS_CONTROL_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "manager.h" 9 | #include "utils.h" 10 | 11 | typedef struct pgs_control_server_ctx_s pgs_control_server_ctx_t; 12 | 13 | struct pgs_control_server_ctx_s { 14 | struct evconnlistener *listener; 15 | pgs_list_t *clients; 16 | 17 | // shared with helper thread 18 | struct event_base *base; 19 | pgs_server_manager_t *sm; 20 | pgs_logger_t *logger; 21 | const pgs_config_t *config; 22 | 23 | // shared from helper thread ctx, 24 | // to access data from helper thread 25 | void *ctx; 26 | }; 27 | 28 | pgs_control_server_ctx_t *pgs_control_server_ctx_new(); 29 | void pgs_control_server_ctx_destroy(pgs_control_server_ctx_t *ptr); 30 | 31 | pgs_control_server_ctx_t * 32 | pgs_control_server_start(int fd, struct event_base *base, 33 | pgs_server_manager_t *sm, pgs_logger_t *logger, 34 | const pgs_config_t *config, void *ctx); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /cmake/FindJeMalloc.cmake: -------------------------------------------------------------------------------- 1 | # - Find JeMalloc library 2 | # Find the native JeMalloc includes and library 3 | # 4 | # JeMalloc_INCLUDE_DIRS - where to find jemalloc.h, etc. 5 | # JeMalloc_LIBRARIES - List of libraries when using jemalloc. 6 | # JeMalloc_FOUND - True if jemalloc found. 7 | 8 | find_path(JeMalloc_INCLUDE_DIRS 9 | NAMES jemalloc/jemalloc.h 10 | HINTS ${JEMALLOC_ROOT_DIR}/include) 11 | 12 | find_library(JeMalloc_LIBRARIES 13 | NAMES jemalloc 14 | HINTS ${JEMALLOC_ROOT_DIR}/lib) 15 | 16 | include(FindPackageHandleStandardArgs) 17 | find_package_handle_standard_args(JeMalloc DEFAULT_MSG JeMalloc_LIBRARIES JeMalloc_INCLUDE_DIRS) 18 | 19 | mark_as_advanced( 20 | JeMalloc_LIBRARIES 21 | JeMalloc_INCLUDE_DIRS) 22 | 23 | if(JeMalloc_FOUND AND NOT (TARGET JeMalloc::JeMalloc)) 24 | add_library (JeMalloc::JeMalloc UNKNOWN IMPORTED) 25 | set_target_properties(JeMalloc::JeMalloc 26 | PROPERTIES 27 | IMPORTED_LOCATION ${JeMalloc_LIBRARIES} 28 | INTERFACE_INCLUDE_DIRECTORIES ${JeMalloc_INCLUDE_DIRS}) 29 | endif() -------------------------------------------------------------------------------- /include/pegasocks/codec/websocket.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_CODEC_WEBSOCKET_H 2 | #define _PGS_CODEC_WEBSOCKET_H 3 | 4 | #include "session/session.h" 5 | 6 | #define pgs_ws_write_head_text(b, l) pgs_ws_write_head(b, l, 0x01) 7 | #define pgs_ws_write_head_bin(b, l) pgs_ws_write_head(b, l, 0x02) 8 | #define pgs_ws_write_text(b, msg, l) pgs_ws_write(b, msg, l, 0x01) 9 | #define pgs_ws_write_bin(b, msg, l) pgs_ws_write(b, msg, l, 0x02) 10 | 11 | typedef struct pgs_ws_resp_s { 12 | int fin; 13 | int opcode; 14 | int mask; 15 | uint64_t payload_len; /* for vmess and trojan, size_t is big enough */ 16 | size_t header_len; 17 | } pgs_ws_resp_t; 18 | 19 | void pgs_ws_req(struct evbuffer *out, const char *hostname, 20 | const char *server_address, int server_port, const char *path); 21 | bool pgs_ws_upgrade_check(const char *data); 22 | void pgs_ws_write_head(struct evbuffer *buf, uint64_t len, int opcode); 23 | void pgs_ws_write(struct evbuffer *buf, uint8_t *msg, uint64_t len, int opcode); 24 | bool pgs_ws_parse_head(uint8_t *data, uint64_t data_len, pgs_ws_resp_t *meta); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /3rd-party/tray/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Serge Zaitsev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/pegasocks/applet.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_APPLET_H 2 | #define _PGS_APPLET_H 3 | 4 | #include 5 | 6 | #include "server/manager.h" 7 | #include "server/local.h" 8 | #include "log.h" 9 | #ifndef _WIN32 10 | #include 11 | #else 12 | #define F_OK 0 13 | #endif 14 | 15 | typedef struct pgs_tray_context_s { 16 | pgs_logger_t *logger; 17 | pgs_server_manager_t *sm; 18 | char *metrics_label; 19 | 20 | void (*quit)(); 21 | } pgs_tray_context_t; 22 | 23 | #ifdef WITH_APPLET 24 | #if defined(_WIN32) || defined(_WIN64) 25 | #define TRAY_WINAPI 1 26 | #elif defined(__linux__) || defined(linux) || defined(__linux) 27 | #define TRAY_APPINDICATOR 1 28 | #elif defined(__APPLE__) || defined(__MACH__) 29 | #define TRAY_APPKIT 1 30 | #endif 31 | #include "tray/tray.h" 32 | 33 | #if TRAY_APPINDICATOR 34 | #define TRAY_ICON "icon.svg" 35 | #elif TRAY_APPKIT 36 | #define TRAY_ICON "icon.png" 37 | #elif TRAY_WINAPI 38 | #define TRAY_ICON "icon.ico" 39 | #endif 40 | 41 | typedef struct tray pgs_tray_t; 42 | typedef struct tray_menu pgs_tray_menu_t; 43 | 44 | void pgs_tray_init(pgs_tray_context_t *ctx); 45 | void pgs_tray_clean(); 46 | void pgs_tray_update(); 47 | #endif 48 | 49 | void pgs_tray_start(pgs_tray_context_t *ctx); 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /include/pegasocks/defs.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_DEFS_H 2 | #define _PGS_DEFS_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #define BUFSIZE_512 512 9 | #define BUFSIZE_16K 16 * 1024 10 | #define DEFAULT_LOG_MPSC_SIZE 256 11 | #define memzero(buf, n) (void)memset(buf, 0, n) 12 | 13 | #define SS_INFO "ss-subkey" 14 | 15 | #define SOCKS5_CMD_IPV4 0x01 16 | #define SOCKS5_CMD_IPV6 0x04 17 | #define SOCKS5_CMD_HOSTNAME 0x03 18 | 19 | #define container_of(ptr, type, member) \ 20 | ({ \ 21 | const typeof(((type *)0)->member) *__mptr = (ptr); \ 22 | (type *)((char *)__mptr - offsetof(type, member)); \ 23 | }) 24 | 25 | typedef void(on_event_cb)(struct bufferevent *bev, short events, void *ctx); 26 | typedef void(on_read_cb)(struct bufferevent *bev, void *ctx); 27 | typedef void(on_udp_read_cb)(int fd, short event, void *ctx); 28 | 29 | // for older version of libevent 30 | #ifndef evuser_new 31 | #define evuser_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg)) 32 | #define evuser_del(ev) event_del(ev) 33 | #define evuser_pending(ev, tv) event_pending((ev), 0, (tv)) 34 | #define evuser_initialized(ev) event_initialized(ev) 35 | #define evuser_trigger(ev) event_active((ev), 0, 0) 36 | #endif 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /include/pegasocks/server/metrics.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_METRICS_H 2 | #define _PGS_METRICS_H 3 | 4 | #include "manager.h" 5 | #include "session/session.h" 6 | #include "codec/codec.h" 7 | #include "ssl.h" 8 | #include "utils.h" 9 | 10 | #include 11 | 12 | #define PGS_FREE_METRICS_TASK(mctx) pgs_list_del(mctx->mtasks, mctx->node) 13 | 14 | typedef struct pgs_metrics_task_ctx_s { 15 | struct event_base *base; 16 | struct evdns_base *dns_base; 17 | const pgs_server_config_t *config; 18 | pgs_server_manager_t *sm; 19 | int server_idx; 20 | pgs_logger_t *logger; 21 | pgs_session_outbound_t *outbound; 22 | struct timeval start_at; 23 | pgs_list_node_t *node; 24 | pgs_list_t *mtasks; 25 | } pgs_metrics_task_ctx_t; 26 | 27 | pgs_metrics_task_ctx_t * 28 | get_metrics_g204_connect(int idx, const pgs_config_t *config, 29 | struct event_base *base, struct evdns_base *dns_base, 30 | pgs_server_manager_t *sm, pgs_logger_t *logger, 31 | pgs_ssl_ctx_t *ssl_ctx, pgs_list_t *mtasks); 32 | 33 | pgs_metrics_task_ctx_t * 34 | pgs_metrics_task_ctx_new(int i, struct event_base *base, 35 | struct evdns_base *dns_base, 36 | const pgs_server_config_t *config, 37 | pgs_server_manager_t *sm, pgs_logger_t *logger, 38 | pgs_session_outbound_t *outbound, pgs_list_t *mtasks); 39 | void pgs_metrics_task_ctx_free(pgs_metrics_task_ctx_t *ptr); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/mpsc.c: -------------------------------------------------------------------------------- 1 | #include "mpsc.h" 2 | #include "assert.h" 3 | 4 | pgs_mpsc_t *pgs_mpsc_new(long size) 5 | { 6 | pgs_mpsc_t *ptr = malloc(sizeof(pgs_mpsc_t)); 7 | ptr->count = ATOMIC_VAR_INIT(0); 8 | ptr->in_pos = ATOMIC_VAR_INIT(0); 9 | 10 | ptr->out_pos = 0; 11 | ptr->mask = size - 1; 12 | ptr->slots = calloc(size, sizeof(void *)); 13 | return ptr; 14 | } 15 | 16 | void pgs_mpsc_free(pgs_mpsc_t *mpsc) 17 | { 18 | free(mpsc->slots); 19 | free(mpsc); 20 | } 21 | 22 | bool pgs_mpsc_send(pgs_mpsc_t *mpsc, void *data) 23 | { 24 | long count = atomic_fetch_add_explicit(&mpsc->count, 1, 25 | memory_order_acquire); 26 | if (count > mpsc->mask) { 27 | atomic_fetch_sub_explicit(&mpsc->count, 1, 28 | memory_order_release); 29 | return false; 30 | } 31 | 32 | long in_pos = atomic_fetch_add_explicit(&mpsc->in_pos, 1, 33 | memory_order_acquire); 34 | void *rv = atomic_exchange_explicit(&mpsc->slots[in_pos & mpsc->mask], 35 | data, memory_order_release); 36 | assert(rv == NULL); 37 | return true; 38 | } 39 | 40 | void *pgs_mpsc_recv(pgs_mpsc_t *mpsc) 41 | { 42 | void *ret = atomic_exchange_explicit(&mpsc->slots[mpsc->out_pos], NULL, 43 | memory_order_acquire); 44 | if (!ret) { 45 | return NULL; 46 | } 47 | if (++mpsc->out_pos > mpsc->mask) 48 | mpsc->out_pos = 0; 49 | long r = atomic_fetch_sub_explicit(&mpsc->count, 1, 50 | memory_order_release); 51 | assert(r > 0); 52 | return ret; 53 | } 54 | -------------------------------------------------------------------------------- /3rd-party/sha3.h: -------------------------------------------------------------------------------- 1 | // sha3.h 2 | // 19-Nov-11 Markku-Juhani O. Saarinen 3 | 4 | #ifndef SHA3_H 5 | #define SHA3_H 6 | 7 | #include 8 | #include 9 | 10 | #ifndef KECCAKF_ROUNDS 11 | #define KECCAKF_ROUNDS 24 12 | #endif 13 | 14 | #ifndef ROTL64 15 | #define ROTL64(x, y) (((x) << (y)) | ((x) >> (64 - (y)))) 16 | #endif 17 | 18 | // state context 19 | typedef struct { 20 | union { // state: 21 | uint8_t b[200]; // 8-bit bytes 22 | uint64_t q[25]; // 64-bit words 23 | } st; 24 | int pt, rsiz, mdlen; // these don't overflow 25 | } sha3_ctx_t; 26 | 27 | // Compression function. 28 | void sha3_keccakf(uint64_t st[25]); 29 | 30 | // OpenSSL - like interfece 31 | int sha3_init(sha3_ctx_t *c, int mdlen); // mdlen = hash output in bytes 32 | int sha3_update(sha3_ctx_t *c, const void *data, size_t len); 33 | int sha3_final(void *md, sha3_ctx_t *c); // digest goes to md 34 | 35 | // compute a sha3 hash (md) of given byte length from "in" 36 | void *sha3(const void *in, size_t inlen, void *md, int mdlen); 37 | 38 | // SHAKE128 and SHAKE256 extensible-output functions 39 | #define shake128_init(c) sha3_init(c, 16) 40 | #define shake256_init(c) sha3_init(c, 32) 41 | #define shake_update sha3_update 42 | 43 | void shake_xof(sha3_ctx_t *c); 44 | void shake_out(sha3_ctx_t *c, void *out, size_t len); 45 | 46 | #endif 47 | 48 | -------------------------------------------------------------------------------- /include/pegasocks/server/local.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_LOCAL_SERVER_H 2 | #define _PGS_LOCAL_SERVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "acl.h" 11 | #include "log.h" 12 | #include "config.h" 13 | #include "manager.h" 14 | #include "ssl.h" 15 | #include "utils.h" 16 | 17 | typedef struct pgs_local_server_s { 18 | uint32_t tid; 19 | int server_fd; 20 | struct event_base *base; 21 | struct evdns_base *dns_base; 22 | struct evconnlistener *listener; 23 | pgs_logger_t *logger; 24 | 25 | // to graceful shutdown 26 | pgs_list_t *sessions; 27 | struct event *ev_term; 28 | 29 | // shared from main thread, read only 30 | pgs_config_t *config; 31 | pgs_server_manager_t *sm; 32 | pgs_acl_t *acl; 33 | pgs_ssl_ctx_t *ssl_ctx; 34 | } pgs_local_server_t; 35 | 36 | typedef struct pgs_local_server_ctx_s { 37 | int fd; 38 | pgs_mpsc_t *mpsc; 39 | pgs_config_t *config; 40 | pgs_server_manager_t *sm; 41 | pgs_acl_t *acl; 42 | pgs_ssl_ctx_t *ssl_ctx; 43 | 44 | void **local_server_ref; /* it will be used to stop the server from other threads */ 45 | } pgs_local_server_ctx_t; 46 | 47 | pgs_local_server_t *pgs_local_server_new(int fd, pgs_mpsc_t *mpsc, 48 | pgs_config_t *config, pgs_acl_t *acl, 49 | pgs_server_manager_t *sm, 50 | pgs_ssl_ctx_t *ssl_ctx); 51 | void pgs_local_server_destroy(pgs_local_server_t *local); 52 | 53 | void *start_local_server(void *data); 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /cmake/FindPCRE.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2007-2009 LuaDist. 2 | # Created by Peter Kapec 3 | # Redistribution and use of this file is allowed according to the terms of the MIT license. 4 | # For details see the COPYRIGHT file distributed with LuaDist. 5 | # Note: 6 | # Searching headers and libraries is very simple and is NOT as powerful as scripts 7 | # distributed with CMake, because LuaDist defines directories to search for. 8 | # Everyone is encouraged to contact the author with improvements. Maybe this file 9 | # becomes part of CMake distribution sometimes. 10 | 11 | # - Find pcre 12 | # Find the native PCRE headers and libraries. 13 | # 14 | # PCRE_INCLUDE_DIRS - where to find pcre.h, etc. 15 | # PCRE_LIBRARIES - List of libraries when using pcre. 16 | # PCRE_FOUND - True if pcre found. 17 | 18 | # Look for the header file. 19 | FIND_PATH(PCRE_INCLUDE_DIR NAMES pcre.h) 20 | 21 | # Look for the library. 22 | FIND_LIBRARY(PCRE_LIBRARY NAMES pcre) 23 | 24 | # Handle the QUIETLY and REQUIRED arguments and set PCRE_FOUND to TRUE if all listed variables are TRUE. 25 | INCLUDE(FindPackageHandleStandardArgs) 26 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(PCRE DEFAULT_MSG PCRE_LIBRARY PCRE_INCLUDE_DIR) 27 | 28 | # Copy the results to the output variables. 29 | IF(PCRE_FOUND) 30 | SET(PCRE_LIBRARIES ${PCRE_LIBRARY}) 31 | SET(PCRE_INCLUDE_DIRS ${PCRE_INCLUDE_DIR}) 32 | ELSE(PCRE_FOUND) 33 | SET(PCRE_LIBRARIES) 34 | SET(PCRE_INCLUDE_DIRS) 35 | ENDIF(PCRE_FOUND) 36 | 37 | MARK_AS_ADVANCED(PCRE_INCLUDE_DIRS PCRE_LIBRARIES) 38 | -------------------------------------------------------------------------------- /include/pegasocks/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_UTILS_H 2 | #define _PGS_UTILS_H 3 | 4 | #include 5 | #include 6 | 7 | #define PGS_DEFAULT_BUFSIZE 1 * 1024 8 | 9 | // ======================== buffers for codec 10 | typedef struct pgs_buffer_s { 11 | uint8_t *buffer; 12 | size_t cap; 13 | } pgs_buffer_t; 14 | 15 | pgs_buffer_t *pgs_buffer_new(); 16 | void pgs_buffer_free(pgs_buffer_t *); 17 | void pgs_buffer_ensure(pgs_buffer_t *, size_t); 18 | 19 | // ======================== list for sessions and outbound metrics requests 20 | typedef struct pgs_list_node_s { 21 | void *val; 22 | 23 | struct pgs_list_node_s *prev; 24 | struct pgs_list_node_s *next; 25 | } pgs_list_node_t; 26 | 27 | typedef struct pgs_list_s { 28 | pgs_list_node_t *head; 29 | pgs_list_node_t *tail; 30 | size_t len; 31 | 32 | void (*free)(void *val); 33 | } pgs_list_t; 34 | 35 | pgs_list_node_t *pgs_list_node_new(void *val); 36 | 37 | pgs_list_t *pgs_list_new(); 38 | void pgs_list_free(pgs_list_t *ptr); 39 | 40 | pgs_list_node_t *pgs_list_add(pgs_list_t *ptr, pgs_list_node_t *node); 41 | 42 | void pgs_list_del(pgs_list_t *ptr, pgs_list_node_t *node); 43 | void pgs_list_del_val(pgs_list_t *ptr, void *val); 44 | 45 | #define pgs_list_foreach(list, cur, _next) \ 46 | for ((cur) = (list)->head, (_next) = (cur) ? ((cur)->next) : (NULL); \ 47 | (cur) != NULL; \ 48 | (cur) = (_next), (_next) = (cur) ? ((cur)->next) : (NULL)) 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /distribution/macos/AppIcon.iconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "16x16", 5 | "idiom": "mac", 6 | "filename": "icon_16x16.png", 7 | "scale": "1x" 8 | }, 9 | { 10 | "size": "16x16", 11 | "idiom": "mac", 12 | "filename": "icon_16x16@2x.png", 13 | "scale": "2x" 14 | }, 15 | { 16 | "size": "32x32", 17 | "idiom": "mac", 18 | "filename": "icon_32x32.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size": "32x32", 23 | "idiom": "mac", 24 | "filename": "icon_32x32@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size": "128x128", 29 | "idiom": "mac", 30 | "filename": "icon_128x128.png", 31 | "scale": "1x" 32 | }, 33 | { 34 | "size": "128x128", 35 | "idiom": "mac", 36 | "filename": "icon_128x128@2x.png", 37 | "scale": "2x" 38 | }, 39 | { 40 | "size": "256x256", 41 | "idiom": "mac", 42 | "filename": "icon_256x256.png", 43 | "scale": "1x" 44 | }, 45 | { 46 | "size": "256x256", 47 | "idiom": "mac", 48 | "filename": "icon_256x256@2x.png", 49 | "scale": "2x" 50 | }, 51 | { 52 | "size": "512x512", 53 | "idiom": "mac", 54 | "filename": "icon_512x512.png", 55 | "scale": "1x" 56 | }, 57 | { 58 | "size": "512x512", 59 | "idiom": "mac", 60 | "filename": "icon_512x512@2x.png", 61 | "scale": "2x" 62 | } 63 | ], 64 | "info": { 65 | "version": 1, 66 | "author": "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Yongsheng Xu 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /WINDOWS_BUILD.md: -------------------------------------------------------------------------------- 1 | ## Windows Build Instructions 2 | 3 | Windows is not supported by design, but with WSL/mingw, we can compile it on windows. 4 | 5 | 6 | ## MSYS2-UCRT64 7 | 8 | Tested on msys2-ucrt64 9 | 10 | ## Dependencies 11 | 12 | > pacman -S mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-openssl pkg-config autoconf automake libtool pkg-config mingw-w64-ucrt-x86_64-mbedtls 13 | 14 | Then I recommend build libevent your self, just use the same toolchain as well. 15 | 16 | 17 | ``` 18 | mkdir -p ./libevent/build 19 | cd ./libevent/build 20 | cmake -DCMAKE_INSTALL_PREFIX=./dist .. 21 | 22 | cmake --build . 23 | 24 | cmake --install . --prefix "./dist" 25 | ``` 26 | 27 | Suppose we have the libevent compiled and installed it to `/c/Users/Bob/repos/libevent/build/dist` 28 | 29 | ## Compile 30 | 31 | Let's say build in the `./build` folder and install path is `./install` 32 | 33 | > mkdir -p ./build && mkdir -p ./install 34 | 35 | Then 36 | 37 | ```bash 38 | cd build 39 | 40 | cmake -DLibevent2_ROOT=/c/Users/Bob/repos/libevent/build/dist -DWITH_APPLET=ON \ 41 | -DCMAKE_INSTALL_PREFIX=../install \ 42 | -DCMAKE_BUILD_TYPE=Release .. 43 | 44 | cmake --build . 45 | 46 | cmake --install . --prefix "../install" 47 | ``` 48 | 49 | At last, we copy other used-dlls from ucrt64 (like `libwinpthread`, `libssl` and `libcrypto`) 50 | 51 | 52 | ```bash 53 | cd ../install/bin 54 | ldd pegas.exe | grep ucrt64 | awk -F\> '{print $2}' | awk -F ' ' '{print $1}' | xargs -I {} cp {} ./ 55 | ``` 56 | 57 | Then put your `config.json` into the `bin` directory, double click `pegas.exe` and it will work. 58 | -------------------------------------------------------------------------------- /include/pegasocks/session/inbound.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_SESSION_INBOUND_H 2 | #define _PGS_SESSION_INBOUND_H 3 | 4 | #include "utils.h" 5 | 6 | #ifndef _WIN32 7 | #include 8 | #endif 9 | 10 | typedef enum { 11 | INBOUND_AUTH, 12 | INBOUND_CMD, 13 | INBOUND_PROXY, 14 | INBOUND_UDP_RELAY, 15 | INBOUND_ERR 16 | } pgs_session_inbound_state; 17 | 18 | typedef struct pgs_session_inbound_s { 19 | struct bufferevent *bev; 20 | pgs_session_inbound_state state; 21 | uint8_t *cmd; /*socks5 cmd*/ 22 | 23 | // udp server and event for udp relay 24 | int udp_fd; 25 | struct sockaddr_in udp_client_addr; 26 | socklen_t udp_client_addr_size; 27 | struct event *udp_server_ev; 28 | uint8_t *udp_rbuf; 29 | int rbuf_pos; 30 | 31 | // bypass udp sessions 32 | pgs_list_t *udp_bypass_sessions; 33 | } pgs_session_inbound_t; 34 | 35 | pgs_session_inbound_t *pgs_session_inbound_new(struct bufferevent *bev); 36 | void pgs_session_inbound_start(pgs_session_inbound_t *inbound, void *ctx); 37 | void pgs_session_inbound_free(pgs_session_inbound_t *sb); 38 | 39 | /* local read handlers 40 | * triggered by readable events and 41 | * when remote server connected 42 | * */ 43 | void on_bypass_local_read(struct bufferevent *bev, void *ctx); 44 | void on_trojan_local_read(struct bufferevent *bev, void *ctx); 45 | void on_v2ray_local_read(struct bufferevent *bev, void *ctx); 46 | void on_ss_local_read(struct bufferevent *bev, void *ctx); 47 | 48 | // UDP 49 | void on_udp_read_trojan(const uint8_t *buf, ssize_t len, void *ctx); 50 | void on_udp_read_v2ray(const uint8_t *buf, ssize_t len, void *ctx); 51 | void on_remote_udp_read(int fd, short event, void *ctx); 52 | // TODO: shadowsocks 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /distribution/macos/Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CFBundleExecutable 7 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 8 | 9 | CFBundleGetInfoString 10 | ${MACOSX_BUNDLE_INFO_STRING} 11 | 12 | 13 | CFBundleIconFile 14 | ${MACOSX_BUNDLE_ICON_FILE} 15 | 16 | 17 | CFBundleIdentifier 18 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 19 | 20 | 21 | CFBundleInfoDictionaryVersion 22 | 6.0 23 | 24 | 25 | CFBundleName 26 | ${MACOSX_BUNDLE_BUNDLE_NAME} 27 | 28 | 29 | CFBundlePackageType 30 | APPL 31 | 32 | 33 | CFBundleShortVersionString 34 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 35 | 36 | CFBundleSignature 37 | ???? 38 | 39 | 40 | CFBundleVersion 41 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 42 | 43 | 44 | CSResourcesFileMapped 45 | 46 | 47 | 48 | NSHighResolutionCapable 49 | 50 | 51 | 52 | NSHumanReadableCopyright 53 | ${MACOSX_BUNDLE_COPYRIGHT} 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories (${CMAKE_SOURCE_DIR}/3rd-party) 2 | include_directories (${CMAKE_SOURCE_DIR}/include) 3 | 4 | set(CONFIG_UTIL_SOURCES 5 | pgs_util_test.c 6 | ${CMAKE_SOURCE_DIR}/3rd-party/sha3.c 7 | ${CMAKE_SOURCE_DIR}/3rd-party/hash_32a.c 8 | ) 9 | 10 | set(CONFIG_TEST_SOURCES 11 | pgs_config_test.c 12 | ${CMAKE_SOURCE_DIR}/src/config.c 13 | ${CMAKE_SOURCE_DIR}/src/log.c 14 | ${CMAKE_SOURCE_DIR}/src/mpsc.c 15 | ${CMAKE_SOURCE_DIR}/src/utils.c 16 | ${CMAKE_SOURCE_DIR}/3rd-party/sha3.c 17 | ${CMAKE_SOURCE_DIR}/3rd-party/hash_32a.c 18 | ${CMAKE_SOURCE_DIR}/3rd-party/parson/parson.c 19 | ) 20 | 21 | if(USE_MBEDTLS) 22 | set(CONFIG_UTIL_SOURCES 23 | ${CONFIG_UTIL_SOURCES} 24 | ${CMAKE_SOURCE_DIR}/src/crypto/mbedtls.c 25 | ) 26 | set(CONFIG_TEST_SOURCES 27 | ${CONFIG_TEST_SOURCES} 28 | ${CMAKE_SOURCE_DIR}/src/crypto/mbedtls.c 29 | ) 30 | else() 31 | set(CONFIG_UTIL_SOURCES 32 | ${CONFIG_UTIL_SOURCES} 33 | ${CMAKE_SOURCE_DIR}/src/crypto/openssl.c 34 | ) 35 | set(CONFIG_TEST_SOURCES 36 | ${CONFIG_TEST_SOURCES} 37 | ${CMAKE_SOURCE_DIR}/src/crypto/openssl.c 38 | ) 39 | endif() 40 | add_executable (pgs_util_test ${CONFIG_UTIL_SOURCES}) 41 | 42 | add_executable (pgs_config_test ${CONFIG_TEST_SOURCES}) 43 | 44 | if(USE_MBEDTLS) 45 | target_link_libraries (pgs_util_test ${MBEDTLS_LIBRARIES}) 46 | target_link_libraries (pgs_config_test ${MBEDTLS_LIBRARIES}) 47 | else() 48 | target_link_libraries (pgs_util_test ${OPENSSL_LIBRARIES}) 49 | target_link_libraries (pgs_config_test ${OPENSSL_LIBRARIES}) 50 | endif() 51 | 52 | add_test (PegaUtilTests pgs_util_test) 53 | add_test (PegaConfigTests pgs_config_test) 54 | -------------------------------------------------------------------------------- /include/pegasocks/server/helper.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_HELPER_THREAD_H 2 | #define _PGS_HELPER_THREAD_H 3 | 4 | #include "event2/bufferevent.h" 5 | #include 6 | #include 7 | 8 | #include "manager.h" 9 | #include "ssl.h" 10 | #include "utils.h" 11 | 12 | typedef void(pgs_timer_cb_t)(evutil_socket_t fd, short event, void *data); 13 | 14 | typedef struct pgs_timer_s { 15 | struct event *ev; 16 | struct timeval tv; 17 | void *ctx; 18 | } pgs_timer_t; 19 | 20 | typedef struct pgs_helper_thread_s { 21 | uint32_t tid; 22 | struct event_base *base; 23 | struct evdns_base *dns_base; 24 | 25 | pgs_timer_t *log_timer; 26 | pgs_timer_t *ping_timer; 27 | 28 | struct event *ev_term; 29 | 30 | pgs_list_t *mtasks; /* metrics tasks */ 31 | 32 | // share 33 | pgs_server_manager_t *sm; 34 | pgs_logger_t *logger; 35 | const pgs_config_t *config; 36 | pgs_ssl_ctx_t *ssl_ctx; 37 | } pgs_helper_thread_t; 38 | 39 | typedef struct pgs_helper_thread_ctx_s { 40 | int cfd; 41 | pgs_config_t *config; 42 | pgs_logger_t *logger; 43 | pgs_server_manager_t *sm; 44 | pgs_ssl_ctx_t *ssl_ctx; 45 | 46 | void **helper_ref; /* use this to exit helper thread later */ 47 | } pgs_helper_thread_ctx_t; 48 | 49 | pgs_helper_thread_t *pgs_helper_thread_new(int cfd, pgs_config_t *config, 50 | pgs_logger_t *logger, 51 | pgs_server_manager_t *sm, 52 | pgs_ssl_ctx_t *ssl_ctx); 53 | void pgs_helper_thread_free(pgs_helper_thread_t *ptr); 54 | 55 | void *pgs_helper_thread_start(void *data); 56 | 57 | pgs_timer_t *pgs_timer_init(int interval, pgs_timer_cb_t, 58 | pgs_helper_thread_t *ptr); 59 | 60 | /* this will ping remote servers and reinit ping timer */ 61 | void pgs_helper_ping_remote(pgs_helper_thread_t *); 62 | 63 | void pgs_timer_destroy(pgs_timer_t *); 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /include/pegasocks/session/session.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_SESSION_H 2 | #define _PGS_SESSION_H 3 | 4 | #ifndef _WIN32 5 | #include 6 | #endif 7 | #include 8 | 9 | #include "server/local.h" 10 | #include "inbound.h" 11 | #include "outbound.h" 12 | #include "utils.h" 13 | 14 | #define pgs_session_debug(session, ...) \ 15 | pgs_logger_debug(session->local_server->logger, __VA_ARGS__) 16 | #define pgs_session_info(session, ...) \ 17 | pgs_logger_info(session->local_server->logger, __VA_ARGS__) 18 | #define pgs_session_warn(session, ...) \ 19 | pgs_logger_warn(session->local_server->logger, __VA_ARGS__) 20 | #define pgs_session_error(session, ...) \ 21 | pgs_logger_error(session->local_server->logger, __VA_ARGS__) 22 | #define pgs_session_debug_buffer(session, buf, len) \ 23 | pgs_logger_debug_buffer(session->local_server->logger, buf, len) 24 | 25 | #define PGS_FREE_SESSION(session) \ 26 | pgs_list_del(session->local_server->sessions, session->node) 27 | 28 | typedef struct pgs_server_session_stats_s { 29 | struct timeval start; 30 | struct timeval end; 31 | uint64_t send; 32 | uint64_t recv; 33 | } pgs_session_stats_t; 34 | 35 | typedef struct pgs_session_s { 36 | pgs_session_inbound_t *inbound; 37 | pgs_session_outbound_t *outbound; 38 | pgs_local_server_t *local_server; 39 | pgs_session_stats_t *metrics; 40 | 41 | pgs_list_node_t *node; /* store the value to sessions */ 42 | } pgs_session_t; 43 | 44 | // session 45 | pgs_session_t *pgs_session_new(int fd, pgs_local_server_t *local_server); 46 | void pgs_session_start(pgs_session_t *session); 47 | void pgs_session_free(pgs_session_t *session); 48 | 49 | // metrics 50 | void on_session_metrics_recv(pgs_session_t *session, uint64_t len); 51 | void on_session_metrics_send(pgs_session_t *session, uint64_t len); 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /acl/genacl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # modified from https://github.com/shadowsocks/shadowsocks-rust/blob/master/acl/genacl_proxy_gfw_bypass_china_ip.py 4 | # use white list mode(proxy_all and bypass_list) 5 | 6 | from urllib import request, parse 7 | import logging 8 | import sys 9 | import json 10 | from datetime import datetime 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | logger = logging.getLogger(__name__) 14 | 15 | GFW_TRANSLATED_URL = "https://raw.githubusercontent.com/NateScarlet/gfwlist.acl/master/gfwlist.acl.json" 16 | CHINA_IP_LIST_URL = "https://raw.githubusercontent.com/17mon/china_ip_list/master/china_ip_list.txt" 17 | CUSTOM_BYPASS = [ 18 | "127.0.0.1", 19 | "10.0.0.0/8", 20 | "172.16.0.0/12", 21 | "192.168.0.0/16", 22 | "fd00::/8", 23 | ] 24 | CUSTOM_PROXY = [ 25 | 26 | ] 27 | 28 | 29 | def fetch_url_content(url): 30 | logger.info("FETCHING {}".format(url)) 31 | r = request.urlopen(url) 32 | return r.read() 33 | 34 | 35 | def write_gfw_list(fp): 36 | gfw_json = fetch_url_content(GFW_TRANSLATED_URL) 37 | gfw_obj = json.loads(gfw_json) 38 | for line in gfw_obj["blacklist"]: 39 | fp.write(line.encode("utf-8")) 40 | fp.write(b"\n") 41 | 42 | 43 | def write_china_ip(fp): 44 | china_ip_list = fetch_url_content(CHINA_IP_LIST_URL) 45 | fp.write(china_ip_list) 46 | fp.write(b"\n") 47 | 48 | 49 | try: 50 | output_file_path = sys.argv[1] 51 | except: 52 | output_file_path = "pegasocks.acl" 53 | 54 | logger.info("WRITING {}".format(output_file_path)) 55 | 56 | with open(output_file_path, 'wb') as fp: 57 | now = datetime.now() 58 | 59 | fp.write(b"# Generated by genacl.py\n") 60 | fp.write("# Time: {}\n".format(now.isoformat()).encode("utf-8")) 61 | fp.write(b"\n") 62 | 63 | fp.write(b"[proxy_all]\n") 64 | fp.write(b"\n[proxy_list]\n") 65 | write_gfw_list(fp) 66 | fp.write(b"\n[bypass_list]\n") 67 | write_china_ip(fp) 68 | 69 | if len(CUSTOM_BYPASS) > 0: 70 | logger.info("CUSTOM_BYPASS {} lines".format(len(CUSTOM_BYPASS))) 71 | fp.write(b"\n[bypass_list]\n") 72 | for a in CUSTOM_BYPASS: 73 | fp.write(a.encode("utf-8")) 74 | fp.write(b"\n") 75 | 76 | if len(CUSTOM_PROXY) > 0: 77 | logger.info("CUSTOM_PROXY {} lines".format(len(CUSTOM_PROXY))) 78 | fp.write(b"\n[proxy_list]\n") 79 | for a in CUSTOM_PROXY: 80 | fp.write(a.encode("utf-8")) 81 | fp.write(b"\n") 82 | 83 | logger.info("DONE") 84 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #ifdef _WIN32 9 | #include 10 | #endif 11 | 12 | #include "pegas.h" 13 | 14 | static bool should_exit = false; 15 | 16 | static void _shutdown(int signum) 17 | { 18 | should_exit = true; 19 | pgs_stop(); 20 | } 21 | 22 | static void restart(int signum) 23 | { 24 | should_exit = false; 25 | pgs_stop(); 26 | } 27 | 28 | int main(int argc, char **argv) 29 | { 30 | #ifdef _WIN32 31 | WSADATA wsa_data; 32 | WSAStartup(0x0201, &wsa_data); 33 | #else 34 | signal(SIGPIPE, SIG_IGN); 35 | signal(SIGUSR1, restart); 36 | #endif 37 | signal(SIGINT, _shutdown); 38 | #ifdef DEBUG_EVENT 39 | event_enable_debug_logging(EVENT_DBG_ALL); 40 | #endif 41 | 42 | // default settings 43 | int server_threads = 4; 44 | char *config_path = NULL; 45 | char *acl_path = NULL; 46 | 47 | // parse opt 48 | int opt = 0; 49 | while ((opt = getopt(argc, argv, "va:c:t:")) != -1) { 50 | switch (opt) { 51 | case 'v': 52 | printf("%s\n", PGS_VERSION); 53 | exit(0); 54 | case 'a': 55 | acl_path = optarg; 56 | break; 57 | case 'c': 58 | config_path = optarg; 59 | break; 60 | case 't': 61 | server_threads = atoi(optarg); 62 | break; 63 | } 64 | } 65 | 66 | // get config path 67 | 68 | char full_config_path[512] = { 0 }; 69 | char config_home[512] = { 0 }; 70 | 71 | if (!config_path) { 72 | #ifdef _WIN32 73 | strcpy(full_config_path, "config.json"); 74 | #else 75 | const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); 76 | const char *home = getenv("HOME"); 77 | if (!xdg_config_home || strlen(xdg_config_home) == 0) { 78 | sprintf(config_home, "%s/.config", home); 79 | } else { 80 | strcpy(config_home, xdg_config_home); 81 | } 82 | sprintf(full_config_path, "%s/.pegasrc", config_home); 83 | if (access(full_config_path, F_OK) == -1) { 84 | sprintf(full_config_path, "%s/pegas/config", 85 | config_home); 86 | if (access(full_config_path, F_OK) == -1) { 87 | fprintf(stderr, "config is required"); 88 | return -1; 89 | } 90 | } 91 | #endif 92 | config_path = full_config_path; 93 | } 94 | 95 | while (!should_exit) { 96 | bool ok = pgs_start( 97 | config_path, acl_path, server_threads, 98 | _shutdown /* used by applet, can be NULL when not use applet */); 99 | 100 | if (!ok) 101 | return -1; 102 | } 103 | 104 | return 0; 105 | } 106 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | 5 | // ===================== buffer 6 | pgs_buffer_t *pgs_buffer_new() 7 | { 8 | pgs_buffer_t *ptr = malloc(sizeof(pgs_buffer_t)); 9 | ptr->buffer = malloc(PGS_DEFAULT_BUFSIZE); 10 | ptr->cap = PGS_DEFAULT_BUFSIZE; 11 | return ptr; 12 | } 13 | 14 | void pgs_buffer_free(pgs_buffer_t *ptr) 15 | { 16 | if (ptr) { 17 | if (ptr->buffer) 18 | free(ptr->buffer); 19 | free(ptr); 20 | } 21 | } 22 | 23 | void pgs_buffer_ensure(pgs_buffer_t *ptr, size_t n) 24 | { 25 | if (ptr->cap >= n) 26 | return; 27 | int times = 1 << 1; 28 | 29 | while (times * ptr->cap < n) { 30 | times <<= 1; 31 | } 32 | ptr->cap = times * ptr->cap; 33 | ptr->buffer = realloc(ptr->buffer, ptr->cap); 34 | } 35 | 36 | // ====================== list 37 | 38 | pgs_list_node_t *pgs_list_node_new(void *val) 39 | { 40 | pgs_list_node_t *ptr = malloc(sizeof(pgs_list_node_t)); 41 | ptr->prev = NULL; 42 | ptr->next = NULL; 43 | ptr->val = val; 44 | return ptr; 45 | } 46 | 47 | pgs_list_t *pgs_list_new() 48 | { 49 | pgs_list_t *ptr = malloc(sizeof(pgs_list_t)); 50 | 51 | ptr->head = NULL; 52 | ptr->tail = NULL; 53 | ptr->free = NULL; 54 | ptr->len = 0; 55 | 56 | return ptr; 57 | } 58 | 59 | void pgs_list_free(pgs_list_t *ptr) 60 | { 61 | while (ptr->len) { 62 | pgs_list_del(ptr, ptr->head); 63 | } 64 | free(ptr); 65 | } 66 | 67 | pgs_list_node_t *pgs_list_add(pgs_list_t *ptr, pgs_list_node_t *node) 68 | { 69 | if (!node) 70 | return NULL; 71 | if (ptr->len) { 72 | node->prev = ptr->tail; 73 | node->next = NULL; 74 | ptr->tail->next = node; 75 | ptr->tail = node; 76 | } else { 77 | ptr->head = node; 78 | ptr->tail = node; 79 | node->prev = NULL; 80 | node->next = NULL; 81 | } 82 | 83 | ++ptr->len; 84 | return node; 85 | } 86 | 87 | void pgs_list_del(pgs_list_t *ptr, pgs_list_node_t *node) 88 | { 89 | if (node->prev) { 90 | node->prev->next = node->next; 91 | } else { 92 | ptr->head = node->next; 93 | } 94 | if (node->next) { 95 | node->next->prev = node->prev; 96 | } else { 97 | ptr->tail = node->prev; 98 | } 99 | 100 | if (ptr->free) 101 | ptr->free(node->val); 102 | 103 | free(node); 104 | 105 | --ptr->len; 106 | } 107 | 108 | void pgs_list_del_val(pgs_list_t *ptr, void *val) 109 | { 110 | pgs_list_node_t *cur = NULL, *next = NULL; 111 | pgs_list_foreach(ptr, cur, next) 112 | { 113 | if (cur->val == val) 114 | break; 115 | } 116 | if (cur != NULL) 117 | pgs_list_del(ptr, cur); 118 | } 119 | -------------------------------------------------------------------------------- /src/server/manager.c: -------------------------------------------------------------------------------- 1 | #include "server/manager.h" 2 | #include 3 | 4 | pgs_server_manager_t * 5 | pgs_server_manager_new(pgs_server_config_t *server_configs, int server_len) 6 | { 7 | pgs_server_manager_t *ptr = malloc(sizeof(pgs_server_manager_t)); 8 | ptr->server_stats = malloc(server_len * sizeof(pgs_server_stats_t)); 9 | for (size_t i = 0; i < server_len; ++i) { 10 | ptr->server_stats[i].connect_delay = -1; 11 | ptr->server_stats[i].g204_delay = -1; 12 | } 13 | ptr->server_configs = server_configs; 14 | ptr->server_len = server_len; 15 | ptr->cur_server_index = 0; 16 | return ptr; 17 | } 18 | 19 | void pgs_server_manager_free(pgs_server_manager_t *ptr) 20 | { 21 | free(ptr->server_stats); 22 | free(ptr); 23 | } 24 | 25 | /* 26 | * Get server config for session 27 | * not thread safe 28 | */ 29 | pgs_server_config_t *pgs_server_manager_get_config(pgs_server_manager_t *sm) 30 | { 31 | assert(sm->cur_server_index < sm->server_len); 32 | return &sm->server_configs[sm->cur_server_index]; 33 | } 34 | 35 | /* 36 | * Get server metrics for session 37 | */ 38 | void pgs_sm_get_servers(pgs_server_manager_t *SM, char *out, int max_len, 39 | int *olen) 40 | { 41 | JSON_Value *vroot = json_value_init_array(); 42 | JSON_Array *aroot = json_value_get_array(vroot); 43 | 44 | for (int i = 0; i < SM->server_len; ++i) { 45 | JSON_Value *vserver = json_value_init_object(); 46 | JSON_Object *oserver = json_value_get_object(vserver); 47 | json_object_set_number(oserver, "index", i); 48 | json_object_set_boolean(oserver, "active", 49 | i == SM->cur_server_index); 50 | json_object_set_number(oserver, "connect", 51 | SM->server_stats[i].connect_delay); 52 | json_object_set_number(oserver, "g204", 53 | SM->server_stats[i].g204_delay); 54 | json_object_set_string(oserver, "type", 55 | SM->server_configs[i].server_type); 56 | json_object_set_string(oserver, "address", 57 | SM->server_configs[i].server_address); 58 | json_object_set_number(oserver, "port", 59 | SM->server_configs[i].server_port); 60 | json_array_append_value(aroot, vserver); 61 | } 62 | 63 | char *serialized_string = NULL; 64 | serialized_string = json_serialize_to_string_pretty(vroot); 65 | *olen = snprintf(out, max_len, "%s", serialized_string); 66 | json_free_serialized_string(serialized_string); 67 | json_value_free(vroot); 68 | } 69 | 70 | bool pgs_sm_set_server(pgs_server_manager_t *SM, int idx) 71 | { 72 | if (idx < SM->server_len && idx >= 0) { 73 | SM->cur_server_index = idx; 74 | return true; 75 | } 76 | return false; 77 | } 78 | -------------------------------------------------------------------------------- /src/codec/trojan.c: -------------------------------------------------------------------------------- 1 | #include "codec/codec.h" 2 | #include "crypto.h" 3 | 4 | #include 5 | 6 | bool trojan_write_remote(pgs_session_t *session, const uint8_t *msg, size_t len, 7 | size_t *olen) 8 | { 9 | struct bufferevent *outbev = session->outbound->bev; 10 | struct evbuffer *outboundw = bufferevent_get_output(outbev); 11 | pgs_outbound_ctx_trojan_t *trojan_s_ctx = session->outbound->ctx; 12 | size_t head_len = trojan_s_ctx->head_len; 13 | if (head_len > 0) { 14 | evbuffer_add(outboundw, trojan_s_ctx->head, head_len); 15 | trojan_s_ctx->head_len = 0; 16 | } 17 | evbuffer_add(outboundw, msg, len); 18 | 19 | pgs_session_debug(session, "local -> remote: %d", len + head_len); 20 | 21 | *olen = len + head_len; 22 | return true; 23 | } 24 | 25 | bool trojan_write_local(pgs_session_t *session, const uint8_t *msg, size_t len, 26 | size_t *olen) 27 | { 28 | uint8_t *udp_packet = NULL; 29 | if (session->inbound->state == INBOUND_PROXY) { 30 | struct bufferevent *inbev = session->inbound->bev; 31 | struct evbuffer *inboundw = bufferevent_get_output(inbev); 32 | evbuffer_add(inboundw, msg, len); 33 | pgs_session_debug(session, "remote -> local: %d", len); 34 | *olen = len; 35 | } else if (session->inbound->state == INBOUND_UDP_RELAY && 36 | session->inbound->udp_fd != -1) { 37 | // decode trojan udp packet 38 | uint8_t atype = msg[0]; 39 | uint16_t addr_len = 1 + 2; // atype + port 40 | addr_len += pgs_get_addr_len(msg); 41 | uint16_t payload_len = msg[addr_len] << 8 | msg[addr_len + 1]; 42 | if (len < (addr_len + 2 + 2 + payload_len) || 43 | msg[addr_len + 2] != '\r' || msg[addr_len + 3] != '\n') { 44 | pgs_session_error( 45 | session, 46 | "payload too large or invalid response"); 47 | goto error; 48 | } 49 | // pack socks5 udp reply 50 | uint16_t udp_packet_len = 2 + 1 + addr_len + payload_len; 51 | udp_packet = malloc(udp_packet_len); 52 | if (udp_packet == NULL) { 53 | pgs_session_error(session, "out of memory"); 54 | goto error; 55 | } 56 | udp_packet[0] = 0x00; 57 | udp_packet[1] = 0x00; 58 | udp_packet[2] = 0x00; 59 | memcpy(udp_packet + 3, msg, addr_len); 60 | memcpy(udp_packet + 3 + addr_len, msg + addr_len + 4, 61 | payload_len); 62 | int n = sendto( 63 | session->inbound->udp_fd, udp_packet, udp_packet_len, 0, 64 | (struct sockaddr *)&session->inbound->udp_client_addr, 65 | session->inbound->udp_client_addr_size); 66 | pgs_session_debug(session, "write %d bytes to local udp sock", 67 | n); 68 | *olen = n; 69 | free(udp_packet); 70 | } 71 | return true; 72 | 73 | error: 74 | if (udp_packet != NULL) { 75 | free(udp_packet); 76 | udp_packet = NULL; 77 | } 78 | return false; 79 | } 80 | -------------------------------------------------------------------------------- /cmake/FindLibevent2.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # https://github.com/sipwise/sems/blob/master/cmake/FindLibevent2.cmake 3 | # 4 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 5 | FIND_PATH(LIBEVENT2_INCLUDE_DIR event2/event.h) 6 | 7 | list(INSERT CMAKE_FIND_LIBRARY_SUFFIXES 0 .imp) 8 | list(INSERT CMAKE_FIND_LIBRARY_SUFFIXES 0 .imp.lib) 9 | 10 | if(CMAKE_CL_64) 11 | FIND_LIBRARY(LIBEVENT2_LIBRARIES NAMES libevent-x64-v120-mt-2_1_4_0 event libevent ) 12 | else() 13 | FIND_LIBRARY(LIBEVENT2_LIBRARIES NAMES libevent-x86-v120-mt-2_1_4_0 event libevent ) 14 | endif() 15 | else() 16 | set(LIBEVENT2_INCLUDE_DIR_SEARCH_PATHS /usr/local/include /usr/include) 17 | set(LIBEVENT2_LIB_SEARCH_PATHS /usr/local/lib /usr/lib) 18 | if(DEFINED Libevent2_ROOT) 19 | set(LIBEVENT2_INCLUDE_DIR_SEARCH_PATHS ${Libevent2_ROOT}/include ${LIBEVENT2_INCLUDE_DIR_SEARCH_PATHS}) 20 | set(LIBEVENT2_LIB_SEARCH_PATHS ${Libevent2_ROOT}/lib ${LIBEVENT2_LIB_SEARCH_PATHS}) 21 | endif() 22 | # -levent -levent_core -levent_extra -levent_openssl 23 | FIND_PATH(LIBEVENT2_INCLUDE_DIR event2/event.h PATHS ${LIBEVENT2_INCLUDE_DIR_SEARCH_PATHS} NO_CMAKE_SYSTEM_PATH) 24 | # OpenBSD issue, lookup from /usr/local/lib to avoid lib mismatch 25 | FIND_LIBRARY(LIBEVENT2_LIBRARIES NAMES event libevent PATHS ${LIBEVENT2_LIB_SEARCH_PATHS} NO_CMAKE_SYSTEM_PATH) 26 | FIND_LIBRARY(LIBEVENT2_CORE_LIBRARIES NAMES event_core libevent_core PATHS ${LIBEVENT2_LIB_SEARCH_PATHS} NO_CMAKE_SYSTEM_PATH) 27 | FIND_LIBRARY(LIBEVENT2_EXTRA_LIBRARIES NAMES event_extra libevent_extra PATHS ${LIBEVENT2_LIB_SEARCH_PATHS} NO_CMAKE_SYSTEM_PATH) 28 | FIND_LIBRARY(LIBEVENT2_SSL_LIBRARIES NAMES event_openssl libevent_openssl PATHS ${LIBEVENT2_LIB_SEARCH_PATHS} NO_CMAKE_SYSTEM_PATH) 29 | FIND_LIBRARY(LIBEVENT2_MBEDTLS_LIBRARIES NAMES event_mbedtls libevent_mbedtls PATHS ${LIBEVENT2_LIB_SEARCH_PATHS}b NO_CMAKE_SYSTEM_PATH) 30 | endif() 31 | 32 | 33 | IF(LIBEVENT2_INCLUDE_DIR AND LIBEVENT2_LIBRARIES) 34 | SET(LIBEVENT2_FOUND TRUE) 35 | ENDIF(LIBEVENT2_INCLUDE_DIR AND LIBEVENT2_LIBRARIES) 36 | 37 | IF(LIBEVENT2_FOUND) 38 | IF (NOT Libevent2_FIND_QUIETLY) 39 | MESSAGE(STATUS "Found libevent2 includes: ${LIBEVENT2_INCLUDE_DIR}/event2/event.h") 40 | MESSAGE(STATUS "Found libevent2 library: ${LIBEVENT2_LIBRARIES}") 41 | MESSAGE(STATUS "Found libevent2 core library: ${LIBEVENT2_CORE_LIBRARIES}") 42 | MESSAGE(STATUS "Found libevent2 extra library: ${LIBEVENT2_EXTRA_LIBRARIES}") 43 | MESSAGE(STATUS "Found libevent2 openssl library: ${LIBEVENT2_SSL_LIBRARIES}") 44 | MESSAGE(STATUS "Found libevent2 mbedtls library: ${LIBEVENT2_MBEDTLS_LIBRARIES}") 45 | ENDIF (NOT Libevent2_FIND_QUIETLY) 46 | ELSE(LIBEVENT2_FOUND) 47 | IF (Libevent2_FIND_REQUIRED) 48 | MESSAGE(FATAL_ERROR "Could NOT find libevent2 development files: ${LIBEVENT2_INCLUDE_DIR} :: ${LIBEVENT2_LIBRARIES}") 49 | ENDIF (Libevent2_FIND_REQUIRED) 50 | ENDIF(LIBEVENT2_FOUND) 51 | 52 | -------------------------------------------------------------------------------- /include/pegasocks/session/udp.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_UDP_H 2 | #define _PGS_UDP_H 3 | 4 | #include "defs.h" 5 | #include "dns.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifndef _WIN32 13 | #include 14 | #include 15 | #endif 16 | 17 | typedef struct pgs_udp_relay_s { 18 | int udp_fd; 19 | uint8_t *udp_rbuf; 20 | struct sockaddr_in udp_server_addr; 21 | struct event *udp_client_ev; 22 | struct timeval timeout; 23 | 24 | // packet header 25 | uint8_t *packet_header; 26 | int packet_header_len; 27 | 28 | // pointer of session pointer 29 | void **session_ptr; 30 | } pgs_udp_relay_t; 31 | 32 | static pgs_udp_relay_t *pgs_udp_relay_new() 33 | { 34 | pgs_udp_relay_t *ptr = malloc(sizeof(pgs_udp_relay_t)); 35 | ptr->udp_fd = 0; 36 | ptr->udp_rbuf = malloc(BUFSIZE_16K * sizeof(uint8_t)); 37 | ptr->udp_client_ev = NULL; 38 | evutil_timerclear(&ptr->timeout); 39 | ptr->timeout.tv_sec = 60; 40 | ptr->session_ptr = malloc(sizeof(void *)); 41 | 42 | ptr->packet_header = NULL; 43 | memzero(&ptr->udp_server_addr, sizeof(struct sockaddr_in)); 44 | 45 | return ptr; 46 | } 47 | 48 | static void pgs_udp_relay_set_header(pgs_udp_relay_t *ptr, const uint8_t *cmd, 49 | int len) 50 | { 51 | ptr->packet_header_len = len; 52 | ptr->packet_header = malloc(len); 53 | memcpy(ptr->packet_header, cmd, len); 54 | } 55 | 56 | static int pgs_udp_relay_trigger( 57 | #ifdef __ANDROID__ 58 | const char *protect_address, int protect_port, 59 | #endif 60 | pgs_udp_relay_t *ptr, const char *host, short port, uint8_t *buf, 61 | ssize_t len, struct event_base *base, on_udp_read_cb *read_cb, 62 | void *session) 63 | { 64 | ptr->udp_fd = socket(AF_INET, SOCK_DGRAM, 0); 65 | 66 | int e = evutil_make_socket_nonblocking(ptr->udp_fd); 67 | if (e) { 68 | perror("evutil_make_socket_nonblocking"); 69 | return e; 70 | } 71 | 72 | *ptr->session_ptr = session; 73 | 74 | ptr->udp_client_ev = event_new(base, ptr->udp_fd, EV_READ | EV_TIMEOUT, 75 | read_cb, ptr); 76 | 77 | event_add(ptr->udp_client_ev, &ptr->timeout); 78 | 79 | ptr->udp_server_addr.sin_family = AF_INET; 80 | int err = inet_pton(AF_INET, host, &ptr->udp_server_addr.sin_addr); 81 | ptr->udp_server_addr.sin_port = htons(port); 82 | if (err <= 0) { 83 | return err; 84 | } 85 | 86 | #ifdef __ANDROID__ 87 | int ret = pgs_protect_fd(ptr->udp_fd, protect_address, protect_port); 88 | if (ret != ptr->udp_fd) { 89 | return -1; 90 | } 91 | #endif 92 | ssize_t n = sendto(ptr->udp_fd, buf, len, 0, 93 | (struct sockaddr *)&ptr->udp_server_addr, 94 | sizeof(ptr->udp_server_addr)); 95 | return n; 96 | } 97 | 98 | static void pgs_udp_relay_free(pgs_udp_relay_t *ptr) 99 | { 100 | if (ptr->udp_fd) 101 | close(ptr->udp_fd); 102 | if (ptr->udp_rbuf != NULL) 103 | free(ptr->udp_rbuf); 104 | if (ptr->udp_client_ev != NULL) 105 | event_free(ptr->udp_client_ev); 106 | if (ptr->packet_header != NULL) 107 | free(ptr->packet_header); 108 | if (ptr->session_ptr) 109 | free(ptr->session_ptr); 110 | if (ptr != NULL) 111 | free(ptr); 112 | } 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /src/session/session.c: -------------------------------------------------------------------------------- 1 | #include "codec/codec.h" 2 | #include "crypto.h" 3 | #include "defs.h" 4 | #include "session/session.h" 5 | #include "server/manager.h" 6 | #include "log.h" 7 | #include "session/udp.h" 8 | #include "session/session.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | /** 17 | * Create New Sesson 18 | * 19 | * @param fd the local socket fd 20 | * @param local_address the local_server object 21 | * which contains logger, base, etc.. 22 | * @return a pointer of new session 23 | */ 24 | pgs_session_t *pgs_session_new(int fd, pgs_local_server_t *local_server) 25 | { 26 | pgs_session_t *ptr = malloc(sizeof(pgs_session_t)); 27 | 28 | struct bufferevent *bev = bufferevent_socket_new(local_server->base, fd, 29 | BEV_OPT_CLOSE_ON_FREE); 30 | ptr->inbound = pgs_session_inbound_new(bev); 31 | ptr->outbound = NULL; 32 | 33 | // init metrics 34 | ptr->metrics = malloc(sizeof(pgs_session_stats_t)); 35 | gettimeofday(&ptr->metrics->start, NULL); 36 | gettimeofday(&ptr->metrics->end, NULL); 37 | ptr->metrics->recv = 0; 38 | ptr->metrics->send = 0; 39 | ptr->local_server = local_server; 40 | 41 | ptr->node = pgs_list_node_new(ptr); 42 | pgs_list_add(local_server->sessions, ptr->node); 43 | 44 | return ptr; 45 | } 46 | 47 | /** 48 | * Start session 49 | * 50 | * it will set event callbacks for local socket fd 51 | * then enable READ event 52 | */ 53 | void pgs_session_start(pgs_session_t *session) 54 | { 55 | pgs_session_inbound_start(session->inbound, session); 56 | } 57 | 58 | void pgs_session_free(pgs_session_t *session) 59 | { 60 | if (session->outbound) { 61 | gettimeofday(&session->metrics->end, NULL); 62 | char tm_start_str[20], tm_end_str[20]; 63 | PARSE_SESSION_TIMEVAL(tm_start_str, session->metrics->start); 64 | PARSE_SESSION_TIMEVAL(tm_end_str, session->metrics->end); 65 | if (session->inbound && 66 | session->inbound->state != INBOUND_UDP_RELAY) { 67 | bool is_ssl_reused = pgs_session_outbound_is_ssl_reused( 68 | session->outbound); 69 | char *prefix = ""; 70 | if (is_ssl_reused) { 71 | prefix = "[REUSED]"; 72 | } 73 | pgs_session_info( 74 | session, 75 | "%sconnection to %s:%d closed, start: %s, end: %s, send: %d, recv: %d", 76 | prefix, session->outbound->dest, 77 | session->outbound->port, tm_start_str, 78 | tm_end_str, session->metrics->send, 79 | session->metrics->recv); 80 | } 81 | if (session->inbound) 82 | pgs_session_inbound_free(session->inbound); 83 | 84 | session->inbound = NULL; 85 | 86 | pgs_session_outbound_free(session->outbound); 87 | } 88 | if (session->inbound) 89 | pgs_session_inbound_free(session->inbound); 90 | 91 | if (session->metrics) 92 | free(session->metrics); 93 | 94 | free(session); 95 | } 96 | 97 | void on_session_metrics_recv(pgs_session_t *session, uint64_t len) 98 | { 99 | if (!session->metrics) 100 | return; 101 | session->metrics->recv += len; 102 | } 103 | 104 | void on_session_metrics_send(pgs_session_t *session, uint64_t len) 105 | { 106 | if (!session->metrics) 107 | return; 108 | session->metrics->send += len; 109 | } 110 | -------------------------------------------------------------------------------- /src/ssl/mbedtls.c: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | #include "ssl.h" 3 | 4 | #include 5 | #include "mbedtls/ctr_drbg.h" 6 | #include "mbedtls/entropy.h" 7 | #include 8 | 9 | #include 10 | 11 | struct pgs_ssl_ctx_s { 12 | mbedtls_ssl_config conf; 13 | mbedtls_entropy_context entropy; 14 | mbedtls_ctr_drbg_context ctr_drbg; 15 | mbedtls_x509_crt cacert; 16 | }; 17 | 18 | pgs_ssl_ctx_t *pgs_ssl_ctx_new(pgs_config_t *config) 19 | { 20 | pgs_ssl_ctx_t *ctx = malloc(sizeof(pgs_ssl_ctx_t)); 21 | 22 | mbedtls_ssl_config_init(&ctx->conf); 23 | mbedtls_ctr_drbg_init(&ctx->ctr_drbg); 24 | mbedtls_entropy_init(&ctx->entropy); 25 | mbedtls_x509_crt_init(&ctx->cacert); 26 | 27 | if (mbedtls_ctr_drbg_seed(&ctx->ctr_drbg, mbedtls_entropy_func, 28 | &ctx->entropy, NULL, 0)) { 29 | goto error; 30 | } 31 | 32 | if (mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT, 33 | MBEDTLS_SSL_TRANSPORT_STREAM, 34 | MBEDTLS_SSL_PRESET_DEFAULT)) { 35 | goto error; 36 | } 37 | 38 | mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, 39 | &ctx->ctr_drbg); 40 | 41 | if (!config->ssl_verify) { 42 | mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE); 43 | } else { 44 | bool cert_loaded = false; 45 | if (config->ssl_crt) { 46 | if (mbedtls_x509_crt_parse_file(&ctx->cacert, 47 | config->ssl_crt) != 0) { 48 | pgs_config_error(config, 49 | "Failed to load cert: %s", 50 | config->ssl_crt); 51 | } else { 52 | cert_loaded = true; 53 | pgs_config_info(config, "cert: %s loaded", 54 | config->ssl_crt); 55 | mbedtls_ssl_conf_ca_chain(&ctx->conf, 56 | &ctx->cacert, NULL); 57 | mbedtls_ssl_conf_authmode( 58 | &ctx->conf, 59 | MBEDTLS_SSL_VERIFY_REQUIRED); 60 | } 61 | } 62 | if (!cert_loaded) { 63 | // fallback 64 | mbedtls_ssl_conf_authmode(&ctx->conf, 65 | MBEDTLS_SSL_VERIFY_NONE); 66 | } 67 | } 68 | 69 | return ctx; 70 | 71 | error: 72 | pgs_ssl_ctx_free(ctx); 73 | return NULL; 74 | } 75 | 76 | void pgs_ssl_ctx_free(pgs_ssl_ctx_t *ctx) 77 | { 78 | mbedtls_x509_crt_free(&ctx->cacert); 79 | mbedtls_ctr_drbg_free(&ctx->ctr_drbg); 80 | mbedtls_entropy_free(&ctx->entropy); 81 | mbedtls_ssl_config_free(&ctx->conf); 82 | free(ctx); 83 | } 84 | 85 | int pgs_session_outbound_ssl_bev_init(struct bufferevent **bev, int fd, 86 | struct event_base *base, 87 | pgs_ssl_ctx_t *ssl_ctx, const char *sni) 88 | { 89 | // notice: should be freed when bev is freed 90 | mbedtls_ssl_context *ssl = malloc(sizeof(mbedtls_ssl_context)); 91 | 92 | mbedtls_ssl_init(ssl); 93 | 94 | int ret = 0; 95 | if ((ret = mbedtls_ssl_setup(ssl, &ssl_ctx->conf)) != 0) { 96 | return -1; 97 | } 98 | if ((ret = mbedtls_ssl_set_hostname(ssl, sni)) != 0) { 99 | return -1; 100 | } 101 | 102 | *bev = bufferevent_mbedtls_socket_new(base, fd, ssl, 103 | BUFFEREVENT_SSL_CONNECTING, 104 | BEV_OPT_DEFER_CALLBACKS); 105 | #ifdef BUFFEREVENT_SSL_BATCH_WRITE 106 | bufferevent_ssl_set_flags(*bev, BUFFEREVENT_SSL_DIRTY_SHUTDOWN | 107 | BUFFEREVENT_SSL_BATCH_WRITE); 108 | #else 109 | bufferevent_mbedtls_set_allow_dirty_shutdown(*bev, 1); 110 | #endif 111 | 112 | return 0; 113 | } 114 | -------------------------------------------------------------------------------- /src/codec/websocket.c: -------------------------------------------------------------------------------- 1 | #include "codec/codec.h" 2 | #include "crypto.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | const char *ws_upgrade = "HTTP/1.1 101"; 9 | const char *ws_key = "dGhlIHNhbXBsZSBub25jZQ=="; 10 | const char *ws_accept = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="; 11 | 12 | void pgs_ws_req(struct evbuffer *out, const char *hostname, 13 | const char *server_address, int server_port, const char *path) 14 | { 15 | // out, hostname, server_address, server_port, path 16 | evbuffer_add_printf(out, "GET %s HTTP/1.1\r\n", path); 17 | evbuffer_add_printf(out, "Host:%s:%d\r\n", hostname, server_port); 18 | evbuffer_add_printf(out, "Upgrade:websocket\r\n"); 19 | evbuffer_add_printf(out, "Connection:upgrade\r\n"); 20 | evbuffer_add_printf(out, "Sec-WebSocket-Key:%s\r\n", ws_key); 21 | evbuffer_add_printf(out, "Sec-WebSocket-Version:13\r\n"); 22 | evbuffer_add_printf( 23 | out, "Origin:https://%s:%d\r\n", server_address, 24 | server_port); //missing this key will lead to 403 response. 25 | evbuffer_add_printf(out, "\r\n"); 26 | } 27 | 28 | bool pgs_ws_upgrade_check(const char *data) 29 | { 30 | return strncmp(data, ws_upgrade, strlen(ws_upgrade)) != 0 || 31 | !strstr(data, ws_accept); 32 | } 33 | 34 | void pgs_ws_write(struct evbuffer *buf, uint8_t *msg, uint64_t len, int opcode) 35 | { 36 | pgs_ws_write_head(buf, len, opcode); 37 | // x ^ 0 = x 38 | evbuffer_add(buf, msg, len); 39 | } 40 | 41 | void pgs_ws_write_head(struct evbuffer *buf, uint64_t len, int opcode) 42 | { 43 | uint8_t a = 0; 44 | a |= 1 << 7; //fin 45 | a |= opcode; 46 | 47 | uint8_t b = 0; 48 | b |= 1 << 7; //mask 49 | 50 | uint16_t c = 0; 51 | uint64_t d = 0; 52 | 53 | //payload len 54 | if (len < 126) { 55 | b |= len; 56 | } else if (len < (1 << 16)) { 57 | b |= 126; 58 | c = htons(len); 59 | } else { 60 | b |= 127; 61 | d = htonll(len); 62 | } 63 | 64 | evbuffer_add(buf, &a, 1); 65 | evbuffer_add(buf, &b, 1); 66 | 67 | if (c) 68 | evbuffer_add(buf, &c, sizeof(c)); 69 | else if (d) 70 | evbuffer_add(buf, &d, sizeof(d)); 71 | 72 | // tls will protect data 73 | // mask data makes nonsense 74 | uint8_t mask_key[4] = { 0, 0, 0, 0 }; 75 | evbuffer_add(buf, &mask_key, 4); 76 | } 77 | 78 | bool pgs_ws_parse_head(uint8_t *data, uint64_t data_len, pgs_ws_resp_t *meta) 79 | { 80 | meta->fin = !!(*data & 0x80); 81 | meta->opcode = *data & 0x0F; 82 | meta->mask = !!(*(data + 1) & 0x80); 83 | meta->payload_len = *(data + 1) & 0x7F; 84 | meta->header_len = 2 + (meta->mask ? 4 : 0); 85 | 86 | if (meta->payload_len < 126) { 87 | if (meta->header_len > data_len) 88 | return false; 89 | 90 | } else if (meta->payload_len == 126) { 91 | meta->header_len += 2; 92 | if (meta->header_len > data_len) 93 | return false; 94 | 95 | meta->payload_len = ntohs(*(uint16_t *)(data + 2)); 96 | 97 | } else if (meta->payload_len == 127) { 98 | meta->header_len += 8; 99 | if (meta->header_len > data_len) 100 | return false; 101 | 102 | meta->payload_len = ntohll(*(uint64_t *)(data + 2)); 103 | } 104 | 105 | if (meta->header_len + meta->payload_len > data_len) 106 | return false; 107 | 108 | const unsigned char *mask_key = data + meta->header_len - 4; 109 | 110 | for (int i = 0; meta->mask && i < meta->payload_len; i++) 111 | data[meta->header_len + i] ^= mask_key[i % 4]; 112 | 113 | return true; 114 | } 115 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static char *log_levels[] = { "DEBUG", "INFO", "WARN", "ERROR" }; 9 | static char *log_colors[] = { "\e[01;32m", "\e[01;32m", "\e[01;35m", 10 | "\e[01;31m" }; 11 | 12 | pgs_logger_msg_t *pgs_logger_msg_new(char *msg, uint32_t tid) 13 | { 14 | pgs_logger_msg_t *ptr = malloc(sizeof(pgs_logger_msg_t)); 15 | ptr->msg = msg; 16 | ptr->tid = tid; 17 | return ptr; 18 | } 19 | void pgs_logger_msg_free(pgs_logger_msg_t *lmsg) 20 | { 21 | free(lmsg->msg); 22 | free(lmsg); 23 | } 24 | 25 | void pgs_logger_debug_buffer(pgs_logger_t *logger, unsigned char *buf, int size) 26 | { 27 | char hexbuf[2 * size + 1]; 28 | for (int i = 0; i < size; i++) { 29 | sprintf(hexbuf + i * 2, "%02x", (int)buf[i]); 30 | } 31 | hexbuf[2 * size] = '\0'; 32 | pgs_logger_debug(logger, "%s", hexbuf); 33 | } 34 | 35 | pgs_logger_t *pgs_logger_new(pgs_mpsc_t *mpsc, LOG_LEVEL level, bool isatty) 36 | { 37 | pgs_logger_t *ptr = malloc(sizeof(pgs_logger_t)); 38 | ptr->level = level; 39 | ptr->mpsc = mpsc; 40 | ptr->tid = (uint32_t)pthread_self(); 41 | ptr->isatty = isatty; 42 | return ptr; 43 | } 44 | 45 | void pgs_logger_free(pgs_logger_t *logger) 46 | { 47 | free(logger); 48 | } 49 | 50 | void pgs_logger_log(LOG_LEVEL level, pgs_logger_t *logger, const char *fmt, ...) 51 | { 52 | if (level < logger->level) { 53 | return; 54 | } 55 | 56 | va_list args; 57 | // construct string, then send to mpsc 58 | // LEVEL date-time tid: MSG 59 | char msg[MAX_MSG_LEN]; 60 | char datetime[20]; 61 | PARSE_TIME_NOW(datetime); 62 | va_start(args, fmt); 63 | int size = vsnprintf(msg, MAX_MSG_LEN - 1, fmt, args); 64 | va_end(args); 65 | 66 | if (size <= 0) 67 | return; 68 | 69 | char *m = malloc(sizeof(char) * (size + 64)); 70 | 71 | if (logger->isatty) { 72 | sprintf(m, "%s%s [thread-%04d] %s: \e[0m%s", 73 | log_colors[level] /*10*/, datetime /*20*/, 74 | (int)(logger->tid % 10000), log_levels[level], msg); 75 | 76 | } else { 77 | sprintf(m, "%s [thread-%04d] %s: %s", datetime, 78 | (int)(logger->tid % 10000), log_levels[level], msg); 79 | } 80 | pgs_logger_msg_t *_msg = pgs_logger_msg_new(m, logger->tid); 81 | 82 | if (!pgs_mpsc_send(logger->mpsc, _msg)) { 83 | // just drop it 84 | pgs_logger_msg_free(_msg); 85 | } 86 | } 87 | 88 | // directly send to log file 89 | // called from main thread 90 | void pgs_logger_main_log(LOG_LEVEL level, FILE *output, const char *fmt, ...) 91 | { 92 | va_list args; 93 | // LEVEL date-time: MSG 94 | char msg[MAX_MSG_LEN - 32]; 95 | char datetime[32]; 96 | PARSE_TIME_NOW(datetime); 97 | va_start(args, fmt); 98 | vsprintf(msg, fmt, args); 99 | va_end(args); 100 | 101 | if (isatty(fileno(output))) { 102 | fprintf(output, "%s%s [thread-main] %s: \e[0m%s\n", 103 | log_colors[level], datetime, log_levels[level], msg); 104 | } else { 105 | fprintf(output, "%s [thread-main] %s: %s\n", datetime, 106 | log_levels[level], msg); 107 | } 108 | 109 | fflush(output); 110 | } 111 | 112 | void pgs_logger_tryrecv(pgs_logger_t *logger, FILE *output) 113 | { 114 | while (true) { 115 | pgs_logger_msg_t *msg = pgs_mpsc_recv(logger->mpsc); 116 | if (msg != NULL) { 117 | fprintf(output, "%s\n", msg->msg); 118 | fflush(output); 119 | pgs_logger_msg_free(msg); 120 | } else { 121 | return; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # pegasocks [![Cirrus CI Build Status](https://api.cirrus-ci.com/github/chux0519/pegasocks.svg)](https://cirrus-ci.com/github/chux0519/pegasocks) 4 | 5 | 是一个基于 socks5 协议的代理客户端,意图在于支持多种类型的代理服务。 6 | C 语言编写,轻量,支持类 unix 系统(Linux/WSL/BSDs/OSX)。 7 | 8 | ⚠️ 这是一个正在开发中的项目,请自行考虑使用成本和风险。 9 | 10 | 中文 | [English](./README.md) 11 | 12 | ## 特点 13 | 14 | 与其他大多数支持多协议的客户端不同,pegasocks 不依赖各种第三方 core(比如 v2ray-core 等),而是真的去实现相关协议的拆装,并且尽可能的照顾性能。因此它 15 | 16 | 1. 🍃 足够轻量,没有 QT 或是 boost 或是其他第三方二进制的依赖。 17 | 2. 🚀 性能优先,默认多个 worker 线程,因此理论上吞吐量会比较高(待benchmark) 18 | 3. 🚥 这是一个 learn by doing 项目,欢迎大家 review 代码,提供优化思路和 C 语言编程相关的指导。 19 | 4. ❌ 没有 GUI,可以直接配合 systemd, launchd, rc 或是各种自定义脚本配置开机启动。后期计划开发一个简单的 tray indicator,在系统的托盘里显示,并且提供一些简单的交互,总之重型的 GUI 是不在考虑范围内的。 20 | 21 | ## 依赖 22 | 23 | - openssl 1.1.1 / mbedtls 2.27.0 24 | - libevent2.12 (OSX 上需要[最新版本](https://github.com/libevent/libevent/tree/master)) 25 | - pcre (lagacy) 可选的,开启 ACL 支持时会需要它 26 | 27 | 其他依赖通过 git submodule 来进行管理,因此需要在获取代码后 28 | 29 | > git submodule update --init 30 | 31 | 或者在 clone 代码时添加 `--recursive` 参数 32 | 33 | ## 安装 34 | 35 | 如果你使用 Arch Linux,可以使用 aur 进行安装 36 | 37 | > yay -S pegasocks-git --overwrite /usr/local/bin/pegas,/usr/local/share/pegasocks/* 38 | 39 | 或者直接编译如下 40 | 41 | ## 编译 42 | 43 | 使用 cmake 44 | 45 | > mkdir build && cd build 46 | > 47 | > cmake -DCMAKE_BUILD_TYPE=Release -DWITH_ACL=ON -DUSE_JEMALLOC=ON .. && make 48 | 49 | ### 可选的 option 有 50 | 51 | |选项|含义|默认值| 52 | | --- | --- | --- | 53 | |-DUSE_MBEDTLS|是否使用 mbedtls 代替 openssl | OFF| 54 | |-DUSE_JEMALLOC|是否使用 jemalloc | OFF| 55 | |-DUSE_STATIC|是否采用 static link | OFF | 56 | |-DWITH_ACL|是否打开 ACL 支持 (这会增加 libcork/ipset/pcre 的依赖,因此会增加程序最终的大小)| OFF | 57 | |-DWITH_APPLET|是否开启系统托盘支持 (这会依赖平台相关的一些系统库,因此会增加程序最终的大小)| OFF | 58 | 59 | 另外可以通过以下参数自定义 JeMalloc/Libevent2/MbedTLS/OpenSSLx/PCRE 的寻找根目录 60 | 61 | > -DOpenSSLx_ROOT=/xxxxxx/xxx/xxx 指定 openssl root 62 | > 63 | > -DLibevent2_ROOT=xxxxxx 指定 libevent root 64 | > 65 | > 以此类推 66 | 67 | ## 运行 68 | 69 | > pegas -c config.json -t 4 70 | 71 | - `-c` 指定配置文件,默认会依次尝试 `$XDG_CONFIG_HOME/.pegasrc` 或者 `$XDG_CONFIG_HOME/pegas/config` 72 | - `-t` 指定工作线程数量,默认为 4 73 | - `-a` 指定 ACL 文件,在开启 ACL 支持后生效 74 | 75 | ## 配置 76 | 77 | 见[配置文档](https://github.com/chux0519/pegasocks/wiki/%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E)或者[手册](https://github.com/chux0519/pegasocks/wiki/manpage) 78 | 79 | 80 | ## 交互 81 | 82 | 通过指定配置文件的 "control_port" 或是 "control_file" 字段,开启 TCP 端口或者 unix socket 和程序进行交互,配合 netcat / socat 与相关的端口或是文件进行交互,支持以下两个命令 83 | 84 | - `GET SERVERS`,返回服务器的信息 85 | - `SET SERVER $idx`,设置当前服务器 86 | 87 | 在 linux 下 socat 演示 88 | 89 | 90 | 91 | 开启系统托盘时,直接使用托盘进行交互,见下 92 | 93 | ## 系统托盘 94 | 95 | 默认编译二进制文件不带 GUI,带上参数 `-DWITH_APPLET=ON` 开启系统托盘功能。 96 | 97 | > cmake -DCMAKE_BUILD_TYPE=Release -DWITH_APPLET=ON .. && make 98 | 99 | ### Linux 100 | 101 | 102 | 103 | 从命令行启动时,将 `logo/icon.svg` 放到 pegas 同级目录,然后正常使用即可。 104 | 105 | 106 | ### OSX 107 | 108 | 109 | 110 | OSX上,默认会将二进制打包成 app bundle,直接将打包出的 `build/PegasApp.app` 复制到应用程序即可。 111 | 112 | ⚠️注:如果遇到无法启动或者启动崩溃的状况,请确认 113 | 114 | 1. 系统安装了最新的 libevent (手动编译 libevent master 分支,不要通过 homebrew,后者提供的是 libevent2.12,在 mac 下会遇到[这个问题](https://github.com/chux0519/pegasocks/issues/23)) 115 | 2. 是否有 **配置文件**,app bundle 会检测 `~/.config/.pegasrc` 或者 `~/.config/pegas/config` 116 | -------------------------------------------------------------------------------- /.cirrus.yml: -------------------------------------------------------------------------------- 1 | freebsd_task: 2 | matrix: 3 | - name: FreeBSD 11.4 4 | freebsd_instance: 5 | image: freebsd-11-4-release-amd64 6 | - name: FreeBSD 12.2 7 | freebsd_instance: 8 | image: freebsd-12-2-release-amd64 9 | env: 10 | HOME: /home/testuser 11 | 12 | install_script: 13 | - sed -i.bak -e 's,pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly,pkg+http://pkg.FreeBSD.org/\${ABI}/latest,' /etc/pkg/FreeBSD.conf 14 | - pkg update -f 15 | - pkg upgrade -y 16 | - pkg install -y gmake cmake openssl libevent git pcre curl unzip 17 | prepare_script: 18 | - git submodule update --init 19 | - curl -L https://github.com/libevent/libevent/archive/refs/tags/release-2.1.12-stable.zip -o libevent.zip 20 | - unzip libevent.zip 21 | - cd libevent-release-2.1.12-stable && cmake -DCMAKE_BUILD_TYPE=Release . && gmake install && cd .. 22 | build_default_test_script: 23 | - mkdir -p build/default 24 | - cd build/default 25 | - cmake ../.. 26 | - gmake 27 | - gmake test 28 | build_acl_test_script: 29 | - mkdir -p build/acl 30 | - cd build/acl 31 | - cmake -DWITH_ACL=ON ../.. 32 | - gmake 33 | - gmake test 34 | 35 | linux_mbedtls_task: # use master branch of libevent to support mbedtls 36 | container: 37 | matrix: 38 | - image: ubuntu:18.04 39 | - image: ubuntu:20.04 40 | 41 | install_script: 42 | - apt update && apt upgrade -y 43 | - DEBIAN_FRONTEND=noninteractive apt install -y unzip git cmake build-essential curl libssl-dev libmbedtls-dev libpcre3-dev 44 | prepare_script: 45 | - git submodule update --init 46 | - curl -L https://github.com/libevent/libevent/archive/refs/heads/master.zip -o libevent.zip 47 | - unzip libevent.zip 48 | - cd libevent-master && cmake -DCMAKE_BUILD_TYPE=Release . && make install && cd .. 49 | - curl -L https://github.com/ARMmbed/mbedtls/archive/refs/tags/v2.27.0.zip -o mbedtls.zip 50 | - unzip mbedtls.zip 51 | - cd mbedtls-2.27.0 && cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_TESTING=OFF -DENABLE_PROGRAMS=OFF . && make install && cd .. 52 | build_default_test_script: 53 | - mkdir -p build/default 54 | - cd build/default 55 | - cmake -DUSE_MBEDTLS=ON ../.. 56 | - make 57 | - make test 58 | build_acl_test_script: 59 | - mkdir -p build/acl 60 | - cd build/acl 61 | - cmake -DUSE_MBEDTLS=ON -DWITH_ACL=ON ../.. 62 | - make 63 | - make test 64 | 65 | linux_openssl_task: 66 | container: 67 | matrix: 68 | - image: ubuntu:18.04 69 | - image: ubuntu:20.04 70 | 71 | install_script: 72 | - apt update && apt upgrade -y 73 | - DEBIAN_FRONTEND=noninteractive apt install -y unzip curl git cmake build-essential libssl-dev libpcre3-dev 74 | prepare_script: 75 | - git submodule update --init 76 | - curl -L https://github.com/libevent/libevent/archive/refs/heads/master.zip -o libevent.zip 77 | - unzip libevent.zip 78 | - cd libevent-master && cmake -DCMAKE_BUILD_TYPE=Release -DEVENT__DISABLE_MBEDTLS=ON . && make install && cd .. 79 | build_default_test_script: 80 | - mkdir -p build/default 81 | - cd build/default 82 | - cmake ../.. 83 | - make 84 | - make test 85 | build_acl_test_script: 86 | - mkdir -p build/acl 87 | - cd build/acl 88 | - cmake -DWITH_ACL=ON ../.. 89 | - make 90 | - make test 91 | -------------------------------------------------------------------------------- /include/pegasocks/log.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_LOG_H 2 | #define _PGS_LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "mpsc.h" 10 | 11 | typedef enum { LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR } LOG_LEVEL; 12 | 13 | typedef struct pgs_logger_s { 14 | pgs_mpsc_t *mpsc; 15 | LOG_LEVEL level; 16 | uint32_t tid; 17 | bool isatty; 18 | } pgs_logger_t; 19 | 20 | typedef struct pgs_logger_msg_s { 21 | char *msg; 22 | uint32_t tid; 23 | } pgs_logger_msg_t; 24 | 25 | typedef struct pgs_logger_server_s { 26 | pgs_logger_t *logger; 27 | FILE *output; 28 | } pgs_logger_server_t; 29 | 30 | #define MAX_MSG_LEN 4096 31 | #define TIME_FORMAT "%Y-%m-%d %H:%M:%S" 32 | #define SESSION_TIME_FORMAT "%H:%M:%S" 33 | #define pgs_logger_debug(logger, ...) \ 34 | pgs_logger_log(LOG_DEBUG, logger, __VA_ARGS__) 35 | #define pgs_logger_info(logger, ...) \ 36 | pgs_logger_log(LOG_INFO, logger, __VA_ARGS__) 37 | #define pgs_logger_warn(logger, ...) \ 38 | pgs_logger_log(LOG_WARN, logger, __VA_ARGS__) 39 | #define pgs_logger_error(logger, ...) \ 40 | pgs_logger_log(LOG_ERROR, logger, __VA_ARGS__) 41 | #define pgs_logger_main_info(fp, ...) \ 42 | pgs_logger_main_log(LOG_INFO, fp, __VA_ARGS__) 43 | #define pgs_logger_main_debug(fp, ...) \ 44 | pgs_logger_main_log(LOG_DEBUG, fp, __VA_ARGS__) 45 | #define pgs_logger_main_error(fp, ...) \ 46 | pgs_logger_main_log(LOG_ERROR, fp, __VA_ARGS__) 47 | #define pgs_logger_main_warn(fp, ...) \ 48 | pgs_logger_main_log(LOG_WARN, fp, __VA_ARGS__) 49 | 50 | #define PARSE_TIME_NOW(buffer) \ 51 | do { \ 52 | time_t t; \ 53 | struct tm *now; \ 54 | time(&t); \ 55 | now = localtime(&t); \ 56 | strftime(buffer, sizeof(buffer), TIME_FORMAT, now); \ 57 | } while (0) 58 | 59 | #define PARSE_SESSION_TIMEVAL(buffer, tv) \ 60 | do { \ 61 | struct tm *tm_info; \ 62 | int millisec = tv.tv_usec / 1000.0; \ 63 | tm_info = localtime(&tv.tv_sec); \ 64 | strftime(buffer, sizeof(buffer), SESSION_TIME_FORMAT, \ 65 | tm_info); \ 66 | sprintf(buffer, "%s.%03d", buffer, millisec); \ 67 | } while (0) 68 | 69 | void pgs_logger_debug_buffer(pgs_logger_t *logger, unsigned char *buf, 70 | int size); 71 | 72 | pgs_logger_t *pgs_logger_new(pgs_mpsc_t *mpsc, LOG_LEVEL level, bool isatty); 73 | void pgs_logger_free(pgs_logger_t *logger); 74 | 75 | // for client, construct and send string to mpsc 76 | void pgs_logger_log(LOG_LEVEL level, pgs_logger_t *logger, const char *fmt, 77 | ...); 78 | 79 | // for main thread 80 | void pgs_logger_main_log(LOG_LEVEL level, FILE *output, const char *fmt, ...); 81 | 82 | // logger thread functions 83 | void pgs_logger_tryrecv(pgs_logger_t *logger, FILE *output); 84 | 85 | pgs_logger_msg_t *pgs_logger_msg_new(char *msg, uint32_t tid); 86 | void pgs_logger_msg_free(pgs_logger_msg_t *lmsg); 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /include/pegasocks/dns.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_SERVER_DNS_H 2 | #define _PGS_SERVER_DNS_H 3 | 4 | #include "config.h" 5 | 6 | #include 7 | 8 | #ifdef __ANDROID__ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static int pgs_protect_fd(int fd, const char *protect_address, int protect_port) 16 | { 17 | int sock; 18 | struct sockaddr_in addr = { 0 }; 19 | sock = socket(AF_INET, SOCK_STREAM, 0); 20 | if (sock == -1) 21 | return -1; 22 | 23 | struct timeval tv; 24 | tv.tv_sec = 3; 25 | tv.tv_usec = 0; 26 | setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, 27 | sizeof(struct timeval)); 28 | setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, 29 | sizeof(struct timeval)); 30 | 31 | addr.sin_family = AF_INET; 32 | addr.sin_port = htons(protect_port); 33 | if (inet_pton(AF_INET, protect_address, &(addr.sin_addr)) != 1) { 34 | close(sock); 35 | return -1; 36 | } 37 | 38 | if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { 39 | close(sock); 40 | return -1; 41 | } 42 | 43 | char buf[4] = { 0 }; 44 | buf[0] = (fd >> 24) & 0xFF; 45 | buf[1] = (fd >> 16) & 0xFF; 46 | buf[2] = (fd >> 8) & 0xFF; 47 | buf[3] = fd & 0xFF; 48 | int n = write(sock, buf, 4); 49 | if (n != 4) { 50 | close(sock); 51 | return -1; 52 | } 53 | 54 | n = read(sock, buf, 4); 55 | if (n != 4) { 56 | close(sock); 57 | return -1; 58 | } 59 | close(sock); 60 | 61 | int ret = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; 62 | if (ret != fd) { 63 | return -1; 64 | } 65 | return fd; 66 | } 67 | #endif 68 | 69 | static void pgs_dns_init(struct event_base *base, 70 | struct evdns_base **dns_base_ptr, pgs_config_t *config, 71 | pgs_logger_t *logger) 72 | { 73 | if ((config)->dns_servers->len > 0) { 74 | *(dns_base_ptr) = evdns_base_new((base), 0); 75 | pgs_list_node_t *cur, *next; 76 | pgs_list_foreach((config)->dns_servers, cur, next) 77 | { 78 | pgs_logger_debug((logger), "Add DNS server: %s", 79 | (const char *)cur->val); 80 | if (evdns_base_nameserver_ip_add( 81 | *(dns_base_ptr), (const char *)cur->val) != 82 | 0) 83 | pgs_logger_error((logger), 84 | "Failed to add DNS server: %s", 85 | (const char *)cur->val); 86 | } 87 | } else { 88 | #ifdef __ANDROID__ 89 | *(dns_base_ptr) = evdns_base_new((base), 0); 90 | #else 91 | #ifdef _WIN32 92 | *(dns_base_ptr) = evdns_base_new((base), 0); 93 | int ret = 94 | evdns_base_config_windows_nameservers(*(dns_base_ptr)); 95 | if (ret == 0) { 96 | pgs_logger_info(logger, 97 | "Found name servers from windows"); 98 | } else { 99 | pgs_logger_error( 100 | logger, 101 | "Failed to find name servers from windows"); 102 | } 103 | #else 104 | // process resolv.conf 105 | *(dns_base_ptr) = evdns_base_new( 106 | (base), EVDNS_BASE_INITIALIZE_NAMESERVERS); 107 | #endif 108 | #endif 109 | } 110 | evdns_base_set_option(*(dns_base_ptr), "max-probe-timeout:", "5"); 111 | evdns_base_set_option(*(dns_base_ptr), "probe-backoff-factor:", "1"); 112 | 113 | #ifdef __ANDROID__ 114 | int count = evdns_base_count_nameservers(*(dns_base_ptr)); 115 | for (int i = 0; i < count; ++i) { 116 | int fd = evdns_base_get_nameserver_fd(*(dns_base_ptr), i); 117 | if (fd > 0) { 118 | int ret = 119 | pgs_protect_fd(fd, 120 | config->android_protect_address, 121 | config->android_protect_port); 122 | if (ret != fd) { 123 | pgs_logger_error( 124 | logger, 125 | "Failed to protect nameserver's fd"); 126 | } else { 127 | pgs_logger_info(logger, 128 | "Nameserver's fd protected"); 129 | } 130 | } 131 | } 132 | #endif 133 | } 134 | 135 | #endif 136 | -------------------------------------------------------------------------------- /src/applet.c: -------------------------------------------------------------------------------- 1 | #include "applet.h" 2 | 3 | #ifdef WITH_APPLET 4 | 5 | static pgs_tray_t tray; 6 | 7 | static bool dirty = false; 8 | 9 | static char full_icon_path[512] = { 0 }; 10 | 11 | void pgs_tray_submenu_update(pgs_tray_context_t *ctx, 12 | pgs_tray_menu_t *servers_submenu); 13 | 14 | static void quit_cb(pgs_tray_menu_t *item) 15 | { 16 | (void)item; 17 | pgs_tray_context_t *ctx = tray.menu[0].context; 18 | ctx->quit(); 19 | tray_exit(); 20 | } 21 | 22 | static void pick_server_cb(pgs_tray_menu_t *item) 23 | { 24 | (void)item; 25 | pgs_tray_context_t *ctx = tray.menu[0].context; 26 | pgs_logger_info(ctx->logger, "switched to server %s, index: %d", 27 | item->text, item->context); 28 | ctx->sm->cur_server_index = item->context; 29 | pgs_tray_submenu_update(ctx, tray.menu[0].submenu); 30 | tray_update(&tray); 31 | } 32 | 33 | static pgs_tray_t tray = { 34 | .icon = TRAY_ICON, 35 | .menu = (pgs_tray_menu_t[]){ { 36 | .text = "servers", 37 | }, 38 | { .text = "-" }, 39 | { .text = "quit", .cb = quit_cb }, 40 | { .text = NULL } }, 41 | }; 42 | 43 | void pgs_tray_submenu_update(pgs_tray_context_t *ctx, 44 | pgs_tray_menu_t *servers_submenu) 45 | { 46 | for (int i = 0; i < ctx->sm->server_len * 3; i += 3) { 47 | int server_idx = i / 3; 48 | servers_submenu[i].text = 49 | ctx->sm->server_configs[server_idx].server_address; 50 | servers_submenu[i].checked = 51 | server_idx == ctx->sm->cur_server_index; 52 | servers_submenu[i].disabled = 0; 53 | servers_submenu[i].cb = pick_server_cb; 54 | servers_submenu[i].context = server_idx; 55 | servers_submenu[i].submenu = NULL; 56 | if (ctx->sm->server_stats[server_idx].connect_delay > 0) { 57 | sprintf(&ctx->metrics_label[256 * server_idx], 58 | "%-8s| connect:%*.0f ms | g204:%*.0f ms", 59 | ctx->sm->server_configs[server_idx].server_type, 60 | 6, 61 | ctx->sm->server_stats[server_idx].connect_delay, 62 | 6, 63 | ctx->sm->server_stats[server_idx].g204_delay); 64 | servers_submenu[i + 1].text = 65 | &ctx->metrics_label[256 * server_idx]; 66 | } else { 67 | servers_submenu[i + 1].text = 68 | ctx->sm->server_configs[server_idx].server_type; 69 | } 70 | servers_submenu[i + 1].disabled = 1; 71 | servers_submenu[i + 1].checked = 0; 72 | servers_submenu[i + 1].submenu = NULL; 73 | servers_submenu[i + 2].text = "-"; 74 | servers_submenu[i + 2].submenu = NULL; 75 | } 76 | servers_submenu[ctx->sm->server_len * 3 - 1].text = NULL; 77 | } 78 | 79 | // init submenu 80 | void pgs_tray_init(pgs_tray_context_t *ctx) 81 | { 82 | pgs_logger_info(ctx->logger, "current server: %d, server length: %d", 83 | ctx->sm->cur_server_index, ctx->sm->server_len); 84 | pgs_tray_menu_t *servers_submenu = 85 | malloc(sizeof(pgs_tray_menu_t) * ctx->sm->server_len * 3); 86 | ctx->metrics_label = malloc(sizeof(char) * 256 * ctx->sm->server_len); 87 | pgs_tray_submenu_update(ctx, servers_submenu); 88 | 89 | char *local_icon_path = "/usr/local/share/pegasocks/logo"; 90 | if (access(local_icon_path, F_OK) == 0) { 91 | sprintf(full_icon_path, "%s/%s", local_icon_path, TRAY_ICON); 92 | tray.icon = full_icon_path; 93 | } 94 | #if defined(TRAY_WINAPI) || defined(TRAY_APPKIT) 95 | tray.icon = TRAY_ICON; 96 | #endif 97 | 98 | tray.menu[0].submenu = servers_submenu; 99 | tray.menu[0].context = ctx; 100 | } 101 | // clean submenu 102 | void pgs_tray_clean() 103 | { 104 | if (tray.menu[0].submenu) 105 | free(tray.menu[0].submenu); 106 | pgs_tray_context_t *ctx = tray.menu[0].context; 107 | if (ctx->metrics_label) 108 | free(ctx->metrics_label); 109 | } 110 | 111 | void pgs_tray_update() 112 | { 113 | if (tray.menu[0].context) { 114 | pgs_tray_submenu_update(tray.menu[0].context, 115 | tray.menu[0].submenu); 116 | dirty = true; 117 | } 118 | } 119 | 120 | void pgs_tray_start(pgs_tray_context_t *ctx) 121 | { 122 | pgs_tray_init(ctx); 123 | if (tray_init(&tray) < 0) { 124 | printf("failed to create tray\n"); 125 | return; 126 | } 127 | while (tray_loop(1) == 0) { 128 | if (dirty) { 129 | tray_update(&tray); 130 | dirty = false; 131 | } 132 | } 133 | pgs_tray_clean(); 134 | } 135 | 136 | #else 137 | void pgs_tray_start(pgs_tray_context_t *ctx) 138 | { 139 | } 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /src/server/helper.c: -------------------------------------------------------------------------------- 1 | #include "server/helper.h" 2 | #include "server/metrics.h" 3 | #include "server/control.h" 4 | #include "dns.h" 5 | 6 | #include "utils.h" 7 | #include 8 | #include 9 | #include 10 | 11 | static void pgs_timer_cb(evutil_socket_t fd, short event, void *data) 12 | { 13 | pgs_timer_t *arg = data; 14 | pgs_helper_thread_t *ctx = arg->ctx; 15 | assert(ctx->logger != NULL); 16 | assert(ctx->config != NULL); 17 | assert(ctx->config->log_file != NULL); 18 | pgs_logger_tryrecv(ctx->logger, ctx->config->log_file); 19 | arg->tv.tv_sec = 1; 20 | arg->tv.tv_usec = 0; 21 | evtimer_add(arg->ev, &arg->tv); 22 | return; 23 | } 24 | 25 | static void pgs_metrics_timer_cb(evutil_socket_t fd, short event, void *data) 26 | { 27 | pgs_timer_t *arg = data; 28 | pgs_helper_thread_t *ctx = arg->ctx; 29 | for (int i = 0; i < ctx->sm->server_len; i++) { 30 | pgs_metrics_task_ctx_t *t = get_metrics_g204_connect( 31 | i, ctx->config, ctx->base, ctx->dns_base, ctx->sm, 32 | ctx->logger, ctx->ssl_ctx, ctx->mtasks); 33 | } 34 | arg->tv.tv_sec = ctx->config->ping_interval; 35 | arg->tv.tv_usec = 0; 36 | evtimer_add(arg->ev, &arg->tv); 37 | 38 | return; 39 | } 40 | 41 | static void pgs_helper_term(int sig, short events, void *arg) 42 | { 43 | event_base_loopbreak(arg); 44 | } 45 | 46 | pgs_timer_t *pgs_timer_init(int interval, pgs_timer_cb_t cb, 47 | pgs_helper_thread_t *ptr) 48 | { 49 | pgs_timer_t *arg = malloc(sizeof(pgs_timer_t)); 50 | arg->tv.tv_sec = interval; 51 | arg->tv.tv_usec = 0; 52 | arg->ctx = ptr; 53 | arg->ev = evtimer_new(ptr->base, cb, (void *)arg); 54 | evtimer_add(arg->ev, &arg->tv); 55 | return arg; 56 | } 57 | 58 | void pgs_timer_destroy(pgs_timer_t *ctx) 59 | { 60 | if (ctx->ev) { 61 | evtimer_del(ctx->ev); 62 | event_free(ctx->ev); 63 | } 64 | if (ctx) 65 | free(ctx); 66 | } 67 | 68 | pgs_helper_thread_t *pgs_helper_thread_new(int cfd, pgs_config_t *config, 69 | pgs_logger_t *logger, 70 | pgs_server_manager_t *sm, 71 | pgs_ssl_ctx_t *ssl_ctx) 72 | { 73 | pgs_helper_thread_t *ptr = malloc(sizeof(pgs_helper_thread_t)); 74 | 75 | struct event_config *cfg = event_config_new(); 76 | event_config_set_flag(cfg, EVENT_BASE_FLAG_NOLOCK); 77 | ptr->base = event_base_new_with_config(cfg); 78 | 79 | pgs_dns_init(ptr->base, &ptr->dns_base, config, logger); 80 | 81 | event_config_free(cfg); 82 | 83 | // timer for logger 84 | ptr->log_timer = pgs_timer_init(1, pgs_timer_cb, ptr); 85 | 86 | // timer for connect and g204, interval can be setted by config 87 | ptr->ping_timer = pgs_timer_init(1, pgs_metrics_timer_cb, ptr); 88 | 89 | ptr->ev_term = evuser_new(ptr->base, pgs_helper_term, ptr->base); 90 | 91 | ptr->mtasks = pgs_list_new(); 92 | ptr->mtasks->free = (void *)pgs_metrics_task_ctx_free; 93 | 94 | ptr->tid = (uint32_t)pthread_self(); 95 | ptr->config = config; 96 | ptr->logger = logger; 97 | ptr->sm = sm; 98 | ptr->ssl_ctx = ssl_ctx; 99 | return ptr; 100 | } 101 | 102 | void pgs_helper_thread_free(pgs_helper_thread_t *ptr) 103 | { 104 | if (ptr->ev_term) { 105 | evuser_del(ptr->ev_term); 106 | event_free(ptr->ev_term); 107 | } 108 | if (ptr->mtasks) { 109 | pgs_list_free(ptr->mtasks); 110 | } 111 | if (ptr->ping_timer) 112 | pgs_timer_destroy(ptr->ping_timer); 113 | if (ptr->log_timer) { 114 | // drain logs first 115 | pgs_logger_tryrecv(ptr->logger, ptr->config->log_file); 116 | pgs_timer_destroy(ptr->log_timer); 117 | } 118 | if (ptr->dns_base) 119 | evdns_base_free(ptr->dns_base, 0); 120 | if (ptr->base) 121 | event_base_free(ptr->base); 122 | free(ptr); 123 | } 124 | 125 | void pgs_helper_ping_remote(pgs_helper_thread_t *helper) 126 | { 127 | if (helper->ping_timer) { 128 | pgs_timer_destroy(helper->ping_timer); 129 | } 130 | helper->ping_timer = pgs_timer_init(0, pgs_metrics_timer_cb, helper); 131 | } 132 | 133 | void *pgs_helper_thread_start(void *data) 134 | { 135 | pgs_helper_thread_ctx_t *ctx = (pgs_helper_thread_ctx_t *)data; 136 | 137 | pgs_helper_thread_t *helper = pgs_helper_thread_new( 138 | ctx->cfd, ctx->config, ctx->logger, ctx->sm, ctx->ssl_ctx); 139 | 140 | *ctx->helper_ref = helper; 141 | 142 | pgs_control_server_ctx_t *control_server = 143 | pgs_control_server_start(ctx->cfd, helper->base, ctx->sm, 144 | ctx->logger, ctx->config, helper); 145 | 146 | event_base_dispatch(helper->base); 147 | 148 | pgs_control_server_ctx_destroy(control_server); 149 | 150 | pgs_helper_thread_free(helper); 151 | 152 | free(ctx); 153 | 154 | return 0; 155 | } 156 | -------------------------------------------------------------------------------- /logo/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 23 | 27 | 28 | 31 | 35 | 36 | 42 | 43 | 66 | 70 | 74 | 75 | 77 | 78 | 80 | image/svg+xml 81 | 83 | 84 | 85 | 86 | 87 | 91 | 95 | 99 | 103 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /3rd-party/hash_32a.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hash_32 - 32 bit Fowler/Noll/Vo FNV-1a hash code 3 | * 4 | * @(#) $Revision: 5.1 $ 5 | * @(#) $Id: hash_32a.c,v 5.1 2009/06/30 09:13:32 chongo Exp $ 6 | * @(#) $Source: /usr/local/src/cmd/fnv/RCS/hash_32a.c,v $ 7 | * 8 | *** 9 | * 10 | * Fowler/Noll/Vo hash 11 | * 12 | * The basis of this hash algorithm was taken from an idea sent 13 | * as reviewer comments to the IEEE POSIX P1003.2 committee by: 14 | * 15 | * Phong Vo (http://www.research.att.com/info/kpv/) 16 | * Glenn Fowler (http://www.research.att.com/~gsf/) 17 | * 18 | * In a subsequent ballot round: 19 | * 20 | * Landon Curt Noll (http://www.isthe.com/chongo/) 21 | * 22 | * improved on their algorithm. Some people tried this hash 23 | * and found that it worked rather well. In an EMail message 24 | * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash. 25 | * 26 | * FNV hashes are designed to be fast while maintaining a low 27 | * collision rate. The FNV speed allows one to quickly hash lots 28 | * of data while maintaining a reasonable collision rate. See: 29 | * 30 | * http://www.isthe.com/chongo/tech/comp/fnv/index.html 31 | * 32 | * for more details as well as other forms of the FNV hash. 33 | *** 34 | * 35 | * To use the recommended 32 bit FNV-1a hash, pass FNV1_32A_INIT as the 36 | * Fnv32_t hashval argument to fnv_32a_buf() or fnv_32a_str(). 37 | * 38 | *** 39 | * 40 | * Please do not copyright this code. This code is in the public domain. 41 | * 42 | * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 43 | * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO 44 | * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR 45 | * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF 46 | * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 47 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 48 | * PERFORMANCE OF THIS SOFTWARE. 49 | * 50 | * By: 51 | * chongo /\oo/\ 52 | * http://www.isthe.com/chongo/ 53 | * 54 | * Share and Enjoy! :-) 55 | */ 56 | 57 | #include 58 | #include "fnv.h" 59 | 60 | 61 | /* 62 | * 32 bit magic FNV-1a prime 63 | */ 64 | #define FNV_32_PRIME ((Fnv32_t)0x01000193) 65 | 66 | 67 | /* 68 | * fnv_32a_buf - perform a 32 bit Fowler/Noll/Vo FNV-1a hash on a buffer 69 | * 70 | * input: 71 | * buf - start of buffer to hash 72 | * len - length of buffer in octets 73 | * hval - previous hash value or 0 if first call 74 | * 75 | * returns: 76 | * 32 bit hash as a static hash type 77 | * 78 | * NOTE: To use the recommended 32 bit FNV-1a hash, use FNV1_32A_INIT as the 79 | * hval arg on the first call to either fnv_32a_buf() or fnv_32a_str(). 80 | */ 81 | Fnv32_t 82 | fnv_32a_buf(void *buf, size_t len, Fnv32_t hval) 83 | { 84 | unsigned char *bp = (unsigned char *)buf; /* start of buffer */ 85 | unsigned char *be = bp + len; /* beyond end of buffer */ 86 | 87 | /* 88 | * FNV-1a hash each octet in the buffer 89 | */ 90 | while (bp < be) { 91 | 92 | /* xor the bottom with the current octet */ 93 | hval ^= (Fnv32_t)*bp++; 94 | 95 | /* multiply by the 32 bit FNV magic prime mod 2^32 */ 96 | #if defined(NO_FNV_GCC_OPTIMIZATION) 97 | hval *= FNV_32_PRIME; 98 | #else 99 | hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); 100 | #endif 101 | } 102 | 103 | /* return our new hash value */ 104 | return hval; 105 | } 106 | 107 | 108 | /* 109 | * fnv_32a_str - perform a 32 bit Fowler/Noll/Vo FNV-1a hash on a string 110 | * 111 | * input: 112 | * str - string to hash 113 | * hval - previous hash value or 0 if first call 114 | * 115 | * returns: 116 | * 32 bit hash as a static hash type 117 | * 118 | * NOTE: To use the recommended 32 bit FNV-1a hash, use FNV1_32A_INIT as the 119 | * hval arg on the first call to either fnv_32a_buf() or fnv_32a_str(). 120 | */ 121 | Fnv32_t 122 | fnv_32a_str(char *str, Fnv32_t hval) 123 | { 124 | unsigned char *s = (unsigned char *)str; /* unsigned string */ 125 | 126 | /* 127 | * FNV-1a hash each octet in the buffer 128 | */ 129 | while (*s) { 130 | 131 | /* xor the bottom with the current octet */ 132 | hval ^= (Fnv32_t)*s++; 133 | 134 | /* multiply by the 32 bit FNV magic prime mod 2^32 */ 135 | #if defined(NO_FNV_GCC_OPTIMIZATION) 136 | hval *= FNV_32_PRIME; 137 | #else 138 | hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); 139 | #endif 140 | } 141 | 142 | /* return our new hash value */ 143 | return hval; 144 | } 145 | -------------------------------------------------------------------------------- /src/server/local.c: -------------------------------------------------------------------------------- 1 | #include "server/local.h" 2 | #include "dns.h" 3 | #include "session/session.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifndef _WIN32 11 | #include 12 | #include 13 | #endif 14 | 15 | static void accept_error_cb(struct evconnlistener *listener, void *ctx) 16 | { 17 | pgs_local_server_t *local = (pgs_local_server_t *)ctx; 18 | 19 | struct event_base *base = local->base; 20 | int err = EVUTIL_SOCKET_ERROR(); 21 | 22 | pgs_logger_debug(local->logger, 23 | "Got an error %d (%s) on the listener." 24 | "Shutting down \n", 25 | err, evutil_socket_error_to_string(err)); 26 | 27 | // after loop exit, outter process have to free the local_server 28 | event_base_loopbreak(base); 29 | } 30 | 31 | static void accept_conn_cb(struct evconnlistener *listener, int fd, 32 | struct sockaddr *address, int socklen, void *ctx) 33 | { 34 | pgs_local_server_t *local = (pgs_local_server_t *)ctx; 35 | struct sockaddr_in *sin = (struct sockaddr_in *)address; 36 | char *ip = inet_ntoa(sin->sin_addr); 37 | 38 | pgs_logger_debug(local->logger, "new client from port %s:%d", ip, 39 | sin->sin_port); 40 | 41 | // new session 42 | pgs_session_t *session = pgs_session_new(fd, local); 43 | 44 | // start session 45 | pgs_session_start(session); 46 | } 47 | 48 | static void pgs_local_server_term(int sig, short events, void *arg) 49 | { 50 | event_base_loopbreak(arg); 51 | } 52 | 53 | /* 54 | * New server, this must be called in a seperate thread 55 | * it will create a base loop without LOCK, so not thread-safe (one loop per thread) 56 | * */ 57 | pgs_local_server_t *pgs_local_server_new(int fd, pgs_mpsc_t *mpsc, 58 | pgs_config_t *config, pgs_acl_t *acl, 59 | pgs_server_manager_t *sm, 60 | pgs_ssl_ctx_t *ssl_ctx) 61 | { 62 | pgs_local_server_t *ptr = malloc(sizeof(pgs_local_server_t)); 63 | 64 | ptr->logger = 65 | pgs_logger_new(mpsc, config->log_level, config->log_isatty); 66 | 67 | // shared across server threads 68 | ptr->server_fd = fd; 69 | ptr->config = config; 70 | ptr->sm = sm; 71 | ptr->acl = acl; 72 | ptr->ssl_ctx = ssl_ctx; 73 | 74 | ptr->tid = (uint32_t)pthread_self(); 75 | 76 | struct event_config *cfg = event_config_new(); 77 | event_config_set_flag(cfg, EVENT_BASE_FLAG_NOLOCK); 78 | ptr->base = event_base_new_with_config(cfg); 79 | event_config_free(cfg); 80 | 81 | pgs_dns_init(ptr->base, &ptr->dns_base, config, ptr->logger); 82 | 83 | ptr->sessions = pgs_list_new(); 84 | ptr->sessions->free = (void *)pgs_session_free; 85 | 86 | // server 87 | ptr->listener = 88 | evconnlistener_new(ptr->base, accept_conn_cb, ptr, 89 | LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 90 | -1, fd); 91 | evconnlistener_set_error_cb(ptr->listener, accept_error_cb); 92 | 93 | ptr->ev_term = evuser_new(ptr->base, pgs_local_server_term, ptr->base); 94 | 95 | return ptr; 96 | } 97 | 98 | // Destroy local server 99 | void pgs_local_server_destroy(pgs_local_server_t *local) 100 | { 101 | if (local->ev_term) { 102 | evuser_del(local->ev_term); 103 | event_free(local->ev_term); 104 | } 105 | if (local->listener) 106 | evconnlistener_free(local->listener); 107 | if (local->dns_base) 108 | evdns_base_free(local->dns_base, 0); 109 | if (local->base) 110 | event_base_free(local->base); 111 | if (local->logger) 112 | pgs_logger_free(local->logger); 113 | free(local); 114 | } 115 | 116 | static void timer_noop(evutil_socket_t fd, short event, void *data) 117 | { 118 | // empty timer, to keep the loop running 119 | } 120 | 121 | /* 122 | * Start new local server 123 | * One Local Server Per Thread 124 | * */ 125 | void *start_local_server(void *data) 126 | { 127 | // pgs_local_server_new(&ctx); 128 | pgs_local_server_ctx_t *ctx = (pgs_local_server_ctx_t *)data; 129 | 130 | pgs_local_server_t *local = 131 | pgs_local_server_new(ctx->fd, ctx->mpsc, ctx->config, ctx->acl, 132 | ctx->sm, ctx->ssl_ctx); 133 | 134 | // it will be used to stop/free pegas from other threads 135 | *ctx->local_server_ref = local; 136 | 137 | pgs_logger_info(local->logger, "Listening at %s:%d", 138 | local->config->local_address, 139 | local->config->local_port); 140 | 141 | // use a dumb timer to keep the event loop, otherwise, the terminate event will not take effect 142 | struct timeval tv; 143 | tv.tv_sec = 1; 144 | tv.tv_usec = 0; 145 | struct event *timer = 146 | event_new(local->base, -1, EV_PERSIST, timer_noop, NULL); 147 | event_add(timer, &tv); 148 | 149 | event_base_dispatch(local->base); 150 | 151 | evtimer_del(timer); 152 | event_free(timer); 153 | 154 | // free all pending/active sessions 155 | pgs_list_free(local->sessions); 156 | 157 | pgs_local_server_destroy(local); 158 | 159 | free(ctx); 160 | 161 | return 0; 162 | } 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # pegasocks [![Cirrus CI Build Status](https://api.cirrus-ci.com/github/chux0519/pegasocks.svg)](https://cirrus-ci.com/github/chux0519/pegasocks) 4 | 5 | is a (socks5)proxy client written in C, intended to support multiple types of proxy protocols(trojan, v2ray, \*shadowsock, ..). 6 | It is lightweight and supports unix-like systems(Linux/WSL/BSDs/OSX). 7 | 8 | ⚠️ This project is under development, please consider the cost and risk of use at your own discretion. 9 | 10 | [中文](./README_zh.md) | English 11 | 12 | ## Features 13 | 14 | Unlike most other clients that support multiple protocols, pegasocks does not rely on various third-party cores (e.g. v2ray-core, etc.), but really goes for the disassembly of the relevant protocols and takes care of performance as much as possible. Therefore it 15 | 16 | 1. 🍃 is light enough that there are no QT or boost or other third-party binary dependencies. 17 | 2. 🚀 Performance-first, with multiple worker threads by default, so theoretically higher throughput to be benchmarked) 18 | 3. 🚥 This is a learn by doing project, feel free to review the code, provide optimization ideas and C programming related guidance. 19 | 4. ❌ There is no GUI, you can directly work with systemd, launchd, rc or various custom scripts toconfigure the bootu.But you can optianly choose to build a simple tray indicator to interact with it, but in short, a heavy-duty GUI is not under consideration. 20 | 21 | ## Dependencies 22 | 23 | - openssl 1.1.1 / mbedtls 2.27.0 24 | - libevent2.12 (on OSX, need the [latest](https://github.com/libevent/libevent/tree/master) version) 25 | - pcre (lagacy) optional,will need it when ACL is enabled 26 | 27 | Other dependencies are managed through git submodule, so you need to run following command after git clone. 28 | 29 | > git submodule update --init 30 | 31 | Or add `--recursive` parameter in `git clone` command. 32 | 33 | ## Install 34 | 35 | If you use Arch Linux, you can install the latest version via AUR 36 | 37 | > yay -S pegasocks-git --overwrite /usr/local/bin/pegas,/usr/local/share/pegasocks/* 38 | 39 | Or you can build it yourself as following 40 | 41 | ## Build 42 | 43 | > mkdir build && cd build 44 | > 45 | > cmake -DCMAKE_BUILD_TYPE=Release -DWITH_ACL=ON -DUSE_JEMALLOC=ON .. && make 46 | 47 | ### Cmake Options 48 | 49 | |option|meaning|default| 50 | | --- | --- | --- | 51 | |-DUSE_MBEDTLS|Whether to use mbedtls instead of openssl| OFF| 52 | |-DUSE_JEMALLOC|Whether to use jemalloc| OFF| 53 | |-DUSE_STATIC|Whether to use static links| OFF | 54 | |-DWITH_ACL|Whether to open ACL support (this will use more dependencies( libcork/ipset/PCRE ), so it will increase the final size of the program)| OFF | 55 | |-DWITH_APPLET|Whether to enable system tray support (this will depend on some system libraries and will therefore increase the final size of the program)| OFF | 56 | 57 | You can also customize the search root of JeMalloc/Libevent2/MbedTLS/OpenSSLx/PCRE with the following parameters. 58 | 59 | > -DOpenSSLx_ROOT=/xxxxxx/xxx/xxx for openssl root 60 | > 61 | > -DLibevent2_ROOT=xxxxxx for libevent root 62 | > 63 | > and so on 64 | 65 | ## Run 66 | 67 | > pegas -c config.json -t 4 68 | 69 | - `-c` specifies the configuration file, by default it will try `$XDG_CONFIG_HOME/.pegasrc` or `$XDG_CONFIG_HOME/pegas/config` in order 70 | - `-t` specifies the number of worker threads, default is 4 71 | - `-a` specifies the ACL file if pegas is build with ACL feature 72 | 73 | ## Configuration 74 | 75 | see [man page](https://github.com/chux0519/pegasocks/wiki/manpage) or [wiki](https://github.com/chux0519/pegasocks/wiki/%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E) 76 | 77 | 78 | ## Interaction 79 | 80 | The "control_port" or "control_file" field of the configuration file can be used to open a TCP port or a unix socket to interact with the program. Use netcat / socat to interact with the relevant port or file. 81 | 82 | - `GET SERVERS`, which will return information about the server 83 | - `SET SERVER $idx`, which sets the current server 84 | 85 | In linux socat demo 86 | 87 | 88 | 89 | Also, the system tray is supported, see below 90 | 91 | ## System Tray 92 | 93 | Default compile binary without GUI, take parameter `-DWITH_APPLET=ON` to enable system tray. 94 | 95 | > cmake -DCMAKE_BUILD_TYPE=Release -DWITH_APPLET=ON . && make 96 | 97 | ### Linux 98 | 99 | 100 | 101 | To show the icon, put `logo/icon.svg` to where pegas sit. 102 | 103 | 104 | ### OSX 105 | 106 | 107 | 108 | On OSX, the binary will be packaged into an app bundle by default, just copy the packaged `build/PegasApp.app` to the application directly. 109 | 110 | ⚠️Note: If you encounter a situation where you can't start or hit a crash, please make sure that 111 | 112 | 1. the latest libevent is installed in your system (do it manually, libevent2.12 from homebew will cause [this issue](https://github.com/chux0519/pegasocks/issues/23)) 113 | 2. if there is a **configuration** file, the app bundle will detect `~/.config/.pegasrc` or `~/.config/pegas/config` 114 | -------------------------------------------------------------------------------- /3rd-party/sha3.c: -------------------------------------------------------------------------------- 1 | // sha3.c 2 | // 19-Nov-11 Markku-Juhani O. Saarinen 3 | 4 | // Revised 07-Aug-15 to match with official release of FIPS PUB 202 "SHA3" 5 | // Revised 03-Sep-15 for portability + OpenSSL - style API 6 | 7 | #include "sha3.h" 8 | 9 | // update the state with given number of rounds 10 | 11 | void sha3_keccakf(uint64_t st[25]) 12 | { 13 | // constants 14 | const uint64_t keccakf_rndc[24] = { 15 | 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 16 | 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, 17 | 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, 18 | 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, 19 | 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 20 | 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 21 | 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, 22 | 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 23 | }; 24 | const int keccakf_rotc[24] = { 25 | 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 26 | 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 27 | }; 28 | const int keccakf_piln[24] = { 29 | 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 30 | 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 31 | }; 32 | 33 | // variables 34 | int i, j, r; 35 | uint64_t t, bc[5]; 36 | 37 | #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ 38 | uint8_t *v; 39 | 40 | // endianess conversion. this is redundant on little-endian targets 41 | for (i = 0; i < 25; i++) { 42 | v = (uint8_t *) &st[i]; 43 | st[i] = ((uint64_t) v[0]) | (((uint64_t) v[1]) << 8) | 44 | (((uint64_t) v[2]) << 16) | (((uint64_t) v[3]) << 24) | 45 | (((uint64_t) v[4]) << 32) | (((uint64_t) v[5]) << 40) | 46 | (((uint64_t) v[6]) << 48) | (((uint64_t) v[7]) << 56); 47 | } 48 | #endif 49 | 50 | // actual iteration 51 | for (r = 0; r < KECCAKF_ROUNDS; r++) { 52 | 53 | // Theta 54 | for (i = 0; i < 5; i++) 55 | bc[i] = st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20]; 56 | 57 | for (i = 0; i < 5; i++) { 58 | t = bc[(i + 4) % 5] ^ ROTL64(bc[(i + 1) % 5], 1); 59 | for (j = 0; j < 25; j += 5) 60 | st[j + i] ^= t; 61 | } 62 | 63 | // Rho Pi 64 | t = st[1]; 65 | for (i = 0; i < 24; i++) { 66 | j = keccakf_piln[i]; 67 | bc[0] = st[j]; 68 | st[j] = ROTL64(t, keccakf_rotc[i]); 69 | t = bc[0]; 70 | } 71 | 72 | // Chi 73 | for (j = 0; j < 25; j += 5) { 74 | for (i = 0; i < 5; i++) 75 | bc[i] = st[j + i]; 76 | for (i = 0; i < 5; i++) 77 | st[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5]; 78 | } 79 | 80 | // Iota 81 | st[0] ^= keccakf_rndc[r]; 82 | } 83 | 84 | #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ 85 | // endianess conversion. this is redundant on little-endian targets 86 | for (i = 0; i < 25; i++) { 87 | v = (uint8_t *) &st[i]; 88 | t = st[i]; 89 | v[0] = t & 0xFF; 90 | v[1] = (t >> 8) & 0xFF; 91 | v[2] = (t >> 16) & 0xFF; 92 | v[3] = (t >> 24) & 0xFF; 93 | v[4] = (t >> 32) & 0xFF; 94 | v[5] = (t >> 40) & 0xFF; 95 | v[6] = (t >> 48) & 0xFF; 96 | v[7] = (t >> 56) & 0xFF; 97 | } 98 | #endif 99 | } 100 | 101 | // Initialize the context for SHA3 102 | 103 | int sha3_init(sha3_ctx_t *c, int mdlen) 104 | { 105 | int i; 106 | 107 | for (i = 0; i < 25; i++) 108 | c->st.q[i] = 0; 109 | c->mdlen = mdlen; 110 | c->rsiz = 200 - 2 * mdlen; 111 | c->pt = 0; 112 | 113 | return 1; 114 | } 115 | 116 | // update state with more data 117 | 118 | int sha3_update(sha3_ctx_t *c, const void *data, size_t len) 119 | { 120 | size_t i; 121 | int j; 122 | 123 | j = c->pt; 124 | for (i = 0; i < len; i++) { 125 | c->st.b[j++] ^= ((const uint8_t *) data)[i]; 126 | if (j >= c->rsiz) { 127 | sha3_keccakf(c->st.q); 128 | j = 0; 129 | } 130 | } 131 | c->pt = j; 132 | 133 | return 1; 134 | } 135 | 136 | // finalize and output a hash 137 | 138 | int sha3_final(void *md, sha3_ctx_t *c) 139 | { 140 | int i; 141 | 142 | c->st.b[c->pt] ^= 0x06; 143 | c->st.b[c->rsiz - 1] ^= 0x80; 144 | sha3_keccakf(c->st.q); 145 | 146 | for (i = 0; i < c->mdlen; i++) { 147 | ((uint8_t *) md)[i] = c->st.b[i]; 148 | } 149 | 150 | return 1; 151 | } 152 | 153 | // compute a SHA-3 hash (md) of given byte length from "in" 154 | 155 | void *sha3(const void *in, size_t inlen, void *md, int mdlen) 156 | { 157 | sha3_ctx_t sha3; 158 | 159 | sha3_init(&sha3, mdlen); 160 | sha3_update(&sha3, in, inlen); 161 | sha3_final(md, &sha3); 162 | 163 | return md; 164 | } 165 | 166 | // SHAKE128 and SHAKE256 extensible-output functionality 167 | 168 | void shake_xof(sha3_ctx_t *c) 169 | { 170 | c->st.b[c->pt] ^= 0x1F; 171 | c->st.b[c->rsiz - 1] ^= 0x80; 172 | sha3_keccakf(c->st.q); 173 | c->pt = 0; 174 | } 175 | 176 | void shake_out(sha3_ctx_t *c, void *out, size_t len) 177 | { 178 | size_t i; 179 | int j; 180 | 181 | j = c->pt; 182 | for (i = 0; i < len; i++) { 183 | if (j >= c->rsiz) { 184 | sha3_keccakf(c->st.q); 185 | j = 0; 186 | } 187 | ((uint8_t *) out)[i] = c->st.b[j++]; 188 | } 189 | c->pt = j; 190 | } 191 | 192 | -------------------------------------------------------------------------------- /src/server/control.c: -------------------------------------------------------------------------------- 1 | #include "server/control.h" 2 | #include "server/helper.h" 3 | #include "server/manager.h" 4 | 5 | #include 6 | #ifndef _WIN32 7 | #include 8 | #include 9 | #endif 10 | 11 | #include 12 | #include 13 | 14 | const char controller_help_msg[] = 15 | "Support commands: PING | GET SERVERS | SET SERVER $idx\n"; 16 | 17 | static void accept_conn_cb(struct evconnlistener *listener, int fd, 18 | struct sockaddr *address, int socklen, void *ctx); 19 | static void accept_error_cb(struct evconnlistener *listener, void *ctx); 20 | static void on_control_read(struct bufferevent *bev, void *ctx); 21 | static void on_control_event(struct bufferevent *bev, short events, void *ctx); 22 | 23 | static bool starts_with(const char *pre, const char *str) 24 | { 25 | return strncasecmp(pre, str, strlen(pre)) == 0; 26 | } 27 | 28 | pgs_control_server_ctx_t * 29 | pgs_control_server_start(int fd, struct event_base *base, 30 | pgs_server_manager_t *sm, pgs_logger_t *logger, 31 | const pgs_config_t *config, void *ctx) 32 | { 33 | pgs_control_server_ctx_t *ptr = pgs_control_server_ctx_new(); 34 | ptr->base = base; 35 | ptr->sm = sm; 36 | ptr->logger = logger; 37 | ptr->config = config; 38 | ptr->ctx = ctx; 39 | 40 | ptr->listener = 41 | evconnlistener_new(base, accept_conn_cb, ptr, 42 | LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 43 | -1, fd); 44 | evconnlistener_set_error_cb(ptr->listener, accept_error_cb); 45 | 46 | ptr->clients = pgs_list_new(); 47 | ptr->clients->free = (void *)bufferevent_free; 48 | 49 | if (config->control_port) { 50 | pgs_logger_info(logger, "Controller Listening at: %s:%d", 51 | config->local_address, config->control_port); 52 | } else if (config->control_file) { 53 | pgs_logger_info(logger, "Controller Listening at: %s", 54 | config->control_file); 55 | } 56 | return ptr; 57 | } 58 | 59 | pgs_control_server_ctx_t *pgs_control_server_ctx_new() 60 | { 61 | pgs_control_server_ctx_t *ptr = 62 | malloc(sizeof(pgs_control_server_ctx_t)); 63 | return ptr; 64 | } 65 | 66 | void pgs_control_server_ctx_destroy(pgs_control_server_ctx_t *ptr) 67 | { 68 | if (ptr->listener) 69 | evconnlistener_free(ptr->listener); 70 | 71 | if (ptr->clients) 72 | pgs_list_free(ptr->clients); 73 | 74 | if (ptr) 75 | free(ptr); 76 | } 77 | 78 | static void accept_error_cb(struct evconnlistener *listener, void *ctx) 79 | { 80 | pgs_control_server_ctx_t *control_ctx = (pgs_control_server_ctx_t *)ctx; 81 | 82 | int err = EVUTIL_SOCKET_ERROR(); 83 | 84 | pgs_logger_debug(control_ctx->logger, 85 | "Got an error %d (%s) on the control pannel listener." 86 | "Shutting down \n", 87 | err, evutil_socket_error_to_string(err)); 88 | 89 | pgs_control_server_ctx_destroy(control_ctx); 90 | } 91 | 92 | static void accept_conn_cb(struct evconnlistener *listener, int fd, 93 | struct sockaddr *address, int socklen, void *ctx) 94 | { 95 | pgs_control_server_ctx_t *control_ctx = (pgs_control_server_ctx_t *)ctx; 96 | struct sockaddr_in *sin = (struct sockaddr_in *)address; 97 | char *ip = inet_ntoa(sin->sin_addr); 98 | 99 | pgs_logger_debug(control_ctx->logger, 100 | "new control client from port %s:%d", ip, 101 | sin->sin_port); 102 | 103 | struct bufferevent *bev = bufferevent_socket_new(control_ctx->base, fd, 104 | BEV_OPT_CLOSE_ON_FREE); 105 | bufferevent_setcb(bev, on_control_read, NULL, on_control_event, ctx); 106 | bufferevent_enable(bev, EV_READ); 107 | 108 | pgs_list_add(control_ctx->clients, pgs_list_node_new(bev)); 109 | } 110 | 111 | static void on_control_read(struct bufferevent *bev, void *ctx) 112 | { 113 | pgs_control_server_ctx_t *control_ctx = (pgs_control_server_ctx_t *)ctx; 114 | // read and parse commands 115 | struct evbuffer *output = bufferevent_get_output(bev); 116 | struct evbuffer *input = bufferevent_get_input(bev); 117 | 118 | uint64_t len = evbuffer_get_length(input); 119 | unsigned char *rdata = evbuffer_pullup(input, len); 120 | 121 | // Support commands are 122 | // PING | GET SERVERS | SET SERVER $idx 123 | if (starts_with("PING", (const char *)rdata)) { 124 | pgs_helper_thread_t *helper = control_ctx->ctx; 125 | pgs_helper_ping_remote(helper); 126 | evbuffer_add(output, "OK\n", 3); 127 | } else if (starts_with("GET SERVERS", (const char *)rdata)) { 128 | pgs_server_config_t *servers = control_ctx->config->servers; 129 | pgs_server_stats_t *stats = control_ctx->sm->server_stats; 130 | int cur_server_index = control_ctx->sm->cur_server_index; 131 | evbuffer_add_printf(output, "\n"); 132 | for (int i = 0; i < control_ctx->config->servers_count; i++) { 133 | if (cur_server_index == i) 134 | evbuffer_add_printf(output, "*"); 135 | else 136 | evbuffer_add_printf(output, " "); 137 | evbuffer_add_printf(output, "%d: %s\n", i, 138 | servers[i].server_address); 139 | evbuffer_add_printf( 140 | output, 141 | "\t%-8s| connect:%*.0f ms | g204:%*.0f ms\n", 142 | servers[i].server_type, 6, 143 | stats[i].connect_delay, 6, stats[i].g204_delay); 144 | } 145 | evbuffer_add_printf(output, "\n"); 146 | } else if (starts_with("SET SERVER", (const char *)rdata)) { 147 | int idx = atoi((const char *)&rdata[10]); 148 | if (idx < control_ctx->config->servers_count) { 149 | // Notice: no lock here 150 | // do not change with systray and control port at the same time 151 | pgs_logger_info( 152 | control_ctx->logger, 153 | "switched to server %s, index: %d", 154 | control_ctx->config->servers[idx].server_address, 155 | idx); 156 | pgs_sm_set_server(control_ctx->sm, idx); 157 | evbuffer_add_printf(output, "OK\n"); 158 | } 159 | 160 | } else { 161 | evbuffer_add(output, controller_help_msg, 162 | strlen(controller_help_msg)); 163 | } 164 | 165 | evbuffer_drain(input, len); 166 | } 167 | 168 | static void on_control_event(struct bufferevent *bev, short events, void *ctx) 169 | { 170 | // free buffer event and related session 171 | pgs_control_server_ctx_t *control_ctx = (pgs_control_server_ctx_t *)ctx; 172 | if (events & BEV_EVENT_ERROR) 173 | pgs_logger_error(control_ctx->logger, "Error from bufferevent"); 174 | if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { 175 | pgs_list_del_val(control_ctx->clients, bev); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /doc/pegas.1.asciidoc: -------------------------------------------------------------------------------- 1 | PEGASOCKS(1) 2 | =========== 3 | :doctype: manpage 4 | 5 | 6 | NAME 7 | ---- 8 | pegas - A lightweight proxy client written in C, intends to support multiple protocols 9 | 10 | 11 | SYNOPSIS 12 | -------- 13 | *pegas* ['OPTIONS'] 14 | 15 | 16 | DESCRIPTION 17 | ----------- 18 | The pegas(1) is a (socks5)proxy client written in C, intended to support multiple types of proxy protocols(trojan, v2ray, *shadowsocks, ..). It is lightweight and supports unix-like systems(Linux/WSL/BSDs/OSX). 19 | 20 | 21 | OPTIONS 22 | ------- 23 | *-a* :: 24 | Specifies the ACL file if pegas is build with ACL feature 25 | 26 | *-c* :: 27 | Specifies the configuration file, by default it will try `$XDG_CONFIG_HOME/.pegasrc` or `$XDG_CONFIG_HOME/pegas/config` in order. 28 | 29 | 30 | *-t* :: 31 | Specifies the number of worker threads, default is 4 32 | 33 | CONFIGS 34 | ------- 35 | 36 | *ROOT* 37 | 38 | The configuration file of pegasocks is in json format, the outermost fields are as follows, the fields with * are optional fields. 39 | 40 | |=== 41 | |Filed |Type |Description 42 | 43 | |servers 44 | |*SERVER* 45 | |Array of *SERVER*, check *SERVER* section for detail. 46 | 47 | |local_address 48 | |string 49 | |For example: 127.0.0.1, 0.0.0.0 50 | 51 | |local_port 52 | |int 53 | |For example: 1080 54 | 55 | |log_level 56 | |int 57 | |_0_(debug), _1_(info), _2_(warn), _3_(error) 58 | 59 | |*log_file 60 | |string 61 | |Log file location. if set, the log will be written to the specified location, if not set, it will be written to stderr by default 62 | 63 | |*ping_interval 64 | |int 65 | |The program will periodically check the network status of the server. Here you can set the check interval. The default is 120, and the unit is second. 66 | 67 | |*control_port 68 | |int 69 | |if set, pegas will listen on the port(TCP), and users can interact with pegas through this port 70 | 71 | |*control_file 72 | |string 73 | |pegas will listen on an unix socket by default(/tmp/pegas.sock), if _control_port_ is set, this option will be ignored 74 | 75 | |*dns_servers 76 | |array of string 77 | |if set, pegas will only use the setted servers to resolve DNS, by default, it will initiate DNS with `EVDNS_BASE_INITIALIZE_NAMESERVERS` of libevent. 78 | 79 | |*ssl.cert 80 | |string (path) 81 | |The crt file to use for ssl, for example: `/etc/ssl/ca-certificates.crt`, `/usr/local/etc/openssl/cert.pem` 82 | 83 | |*ssl.verify 84 | |boolean 85 | |If set to false, it will skip ssl verification, default to true. 86 | 87 | |*android.protect_address 88 | |string 89 | |Protect server address(Android only). 90 | 91 | |*android.protect_port 92 | |int 93 | |Protect server port(Android only, pass fd in, do protection and return the fd out, return -1 if failed). 94 | 95 | 96 | |=== 97 | 98 | *SERVER* 99 | 100 | All types of server have some common fields 101 | 102 | |=== 103 | |Filed |Type |Description 104 | 105 | |server_type 106 | |string 107 | |Type of server, _v2ray_, _trojan_ and _shadowsocks_ are supported now 108 | 109 | |server_address 110 | |string 111 | |IP or hostname of your server 112 | 113 | |server_port 114 | |string 115 | | 116 | 117 | |password 118 | |string 119 | |for _v2ray_, it's uuid, for _shadowsocks_ and _trojan_, it's the password 120 | 121 | |=== 122 | 123 | *V2RAY* 124 | 125 | If server_type is _v2ray_, there're some other fields 126 | 127 | |=== 128 | |Filed |Type |Description 129 | 130 | |secure 131 | |string 132 | |Encryption algorithm of vmess, supports _aes-128-cfb_, _aes-128-gcm_ and _chacha20-poly1305_ , default to _aes-128-cfb_ 133 | 134 | |ssl.sni 135 | |string 136 | |If ssl is enabled, the sni of remote server 137 | 138 | |websocket.path 139 | |string 140 | |If websocket is enabled, the path of remote server 141 | 142 | |websocket.hostname 143 | |string 144 | 145 | |=== 146 | 147 | *TROJAN* 148 | 149 | If server_type is _trojan_, there're some other fields 150 | 151 | |=== 152 | |Filed |Type |Description 153 | 154 | |websocket.path 155 | |string 156 | |If websocket is enabled, the path of remote server 157 | 158 | |websocket.hostname 159 | |string 160 | 161 | |=== 162 | 163 | *SHADOWSOCKS* 164 | 165 | If server_type is _shadowsocks_, there're some other fields 166 | 167 | |=== 168 | |Filed |Type |Description 169 | 170 | |method 171 | |string 172 | |The crypto method, _aes-128-cfb_,_aes-128-gcm_, _aes-256-gcm_ and _chacha20-poly1305_ are supported, default to _aes-128-gcm_ 173 | 174 | |=== 175 | 176 | PS: UDP and plugin support is WIP 177 | 178 | *EXAMPLES* 179 | 180 | *trojan-gfw* 181 | 182 | [source,JSON] 183 | ---- 184 | { 185 | "servers": [ 186 | { 187 | "server_address": "yourhost.com", 188 | "server_type": "trojan", 189 | "server_port": 443, 190 | "password": "password" 191 | } 192 | ], 193 | "local_address": "0.0.0.0", 194 | "local_port": 1080, 195 | "log_level": 1 196 | } 197 | ---- 198 | 199 | *trojan-go* 200 | 201 | [source,JSON] 202 | ---- 203 | { 204 | "servers": [ 205 | { 206 | "server_address": "yourhost.com", 207 | "server_type": "trojan", 208 | "server_port": 443, 209 | "password": "password", 210 | "websocket": { 211 | "path": "/trojan", 212 | "hostname": "yourhost.com" 213 | } 214 | } 215 | ], 216 | "local_address": "0.0.0.0", 217 | "local_port": 1080, 218 | "log_level": 1 219 | } 220 | ---- 221 | 222 | *v2ray + tls + websocket* 223 | 224 | [source,JSON] 225 | ---- 226 | { 227 | "servers": [ 228 | { 229 | "server_address": "yourhost.com", 230 | "server_type": "v2ray", 231 | "server_port": 443, 232 | "password": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 233 | "ssl": {"sni": "ray.yourhost.com"}, 234 | "websocket": { 235 | "path": "/ray", 236 | "hostname": "yourhost.com" 237 | } 238 | } 239 | ], 240 | "local_address": "0.0.0.0", 241 | "local_port": 1080, 242 | "log_level": 1 243 | } 244 | ---- 245 | 246 | 247 | *v2ray + tcp* 248 | 249 | [source,JSON] 250 | ---- 251 | { 252 | "servers": [ 253 | { 254 | "server_address": "xxxxx.jamjams.net", 255 | "server_type": "v2ray", 256 | "server_port": 10086, 257 | "secure": "aes-128-gcm", 258 | "password": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 259 | } 260 | ], 261 | "local_address": "0.0.0.0", 262 | "local_port": 1080, 263 | "log_level": 1 264 | } 265 | ---- 266 | 267 | 268 | AUTHOR 269 | ------ 270 | Yongsheng Xu 271 | 272 | 273 | RESOURCES 274 | --------- 275 | GitHub: 276 | 277 | -------------------------------------------------------------------------------- /cmake/FindMbedTLS.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2017-2019 AVSystem 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #.rst: 16 | # FindMbedTLS 17 | # ----------- 18 | # 19 | # Find the mbedTLS encryption library. 20 | # 21 | # Imported Targets 22 | # ^^^^^^^^^^^^^^^^ 23 | # 24 | # This module defines the following :prop_tgt:`IMPORTED` targets: 25 | # 26 | # ``mbedtls`` 27 | # The mbedTLS ``mbedtls`` library, if found. 28 | # ``mbedcrypto`` 29 | # The mbedtls ``crypto`` library, if found. 30 | # ``mbedx509`` 31 | # The mbedtls ``x509`` library, if found. 32 | # 33 | # Result Variables 34 | # ^^^^^^^^^^^^^^^^ 35 | # 36 | # This module will set the following variables in your project: 37 | # 38 | # ``MBEDTLS_FOUND`` 39 | # System has the mbedTLS library. 40 | # ``MBEDTLS_INCLUDE_DIR`` 41 | # The mbedTLS include directory. 42 | # ``MBEDTLS_LIBRARY`` 43 | # The mbedTLS SSL library. 44 | # ``MBEDTLS_CRYPTO_LIBRARY`` 45 | # The mbedTLS crypto library. 46 | # ``MBEDTLS_X509_LIBRARY`` 47 | # The mbedTLS x509 library. 48 | # ``MBEDTLS_LIBRARIES`` 49 | # All mbedTLS libraries. 50 | # ``MBEDTLS_VERSION`` 51 | # This is set to ``$major.$minor.$patch``. 52 | # ``MBEDTLS_VERSION_MAJOR`` 53 | # Set to major mbedTLS version number. 54 | # ``MBEDTLS_VERSION_MINOR`` 55 | # Set to minor mbedTLS version number. 56 | # ``MBEDTLS_VERSION_PATCH`` 57 | # Set to patch mbedTLS version number. 58 | # 59 | # Hints 60 | # ^^^^^ 61 | # 62 | # Set ``MBEDTLS_ROOT_DIR`` to the root directory of an mbedTLS installation. 63 | # Set ``MBEDTLS_USE_STATIC_LIBS`` to ``TRUE`` to look for static libraries. 64 | 65 | if(MBEDTLS_ROOT_DIR) 66 | # Disable re-rooting paths in find_path/find_library. 67 | # This assumes MBEDTLS_ROOT_DIR is an absolute path. 68 | set(_EXTRA_FIND_ARGS "NO_CMAKE_FIND_ROOT_PATH") 69 | endif() 70 | 71 | find_path(MBEDTLS_INCLUDE_DIR 72 | NAMES mbedtls/ssl.h 73 | PATH_SUFFIXES include 74 | HINTS ${MBEDTLS_ROOT_DIR} 75 | ${_EXTRA_FIND_ARGS}) 76 | 77 | # based on https://github.com/ARMmbed/mbedtls/issues/298 78 | if(MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h") 79 | file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" VERSION_STRING_LINE REGEX "^#define MBEDTLS_VERSION_STRING[ \\t\\n\\r]+\"[^\"]*\"$") 80 | file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" VERSION_MAJOR_LINE REGEX "^#define MBEDTLS_VERSION_MAJOR[ \\t\\n\\r]+[0-9]+$") 81 | file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" VERSION_MINOR_LINE REGEX "^#define MBEDTLS_VERSION_MINOR[ \\t\\n\\r]+[0-9]+$") 82 | file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" VERSION_PATCH_LINE REGEX "^#define MBEDTLS_VERSION_PATCH[ \\t\\n\\r]+[0-9]+$") 83 | 84 | string(REGEX REPLACE "^#define MBEDTLS_VERSION_STRING[ \\t\\n\\r]+\"([^\"]*)\"$" "\\1" MBEDTLS_VERSION "${VERSION_STRING_LINE}") 85 | string(REGEX REPLACE "^#define MBEDTLS_VERSION_MAJOR[ \\t\\n\\r]+([0-9]+)$" "\\1" MBEDTLS_VERSION_MAJOR "${VERSION_MAJOR_LINE}") 86 | string(REGEX REPLACE "^#define MBEDTLS_VERSION_MINOR[ \\t\\n\\r]+([0-9]+)$" "\\1" MBEDTLS_VERSION_MINOR "${VERSION_MINOR_LINE}") 87 | string(REGEX REPLACE "^#define MBEDTLS_VERSION_PATCH[ \\t\\n\\r]+([0-9]+)$" "\\1" MBEDTLS_VERSION_PATCH "${VERSION_PATCH_LINE}") 88 | endif() 89 | 90 | 91 | if(MBEDTLS_USE_STATIC_LIBS) 92 | set(_MBEDTLS_LIB_NAME libmbedtls.a) 93 | set(_MBEDTLS_CRYPTO_LIB_NAME libmbedcrypto.a) 94 | set(_MBEDTLS_X509_LIB_NAME libmbedx509.a) 95 | else() 96 | set(_MBEDTLS_LIB_NAME mbedtls) 97 | set(_MBEDTLS_CRYPTO_LIB_NAME mbedcrypto) 98 | set(_MBEDTLS_X509_LIB_NAME mbedx509) 99 | endif() 100 | 101 | find_library(MBEDTLS_LIBRARY 102 | NAMES ${_MBEDTLS_LIB_NAME} 103 | PATH_SUFFIXES lib 104 | HINTS ${MBEDTLS_ROOT_DIR} 105 | ${_EXTRA_FIND_ARGS}) 106 | 107 | find_library(MBEDTLS_CRYPTO_LIBRARY 108 | NAMES ${_MBEDTLS_CRYPTO_LIB_NAME} 109 | PATH_SUFFIXES lib 110 | HINTS ${MBEDTLS_ROOT_DIR} 111 | ${_EXTRA_FIND_ARGS}) 112 | 113 | find_library(MBEDTLS_X509_LIBRARY 114 | NAMES ${_MBEDTLS_X509_LIB_NAME} 115 | PATH_SUFFIXES lib 116 | HINTS ${MBEDTLS_ROOT_DIR} 117 | ${_EXTRA_FIND_ARGS}) 118 | 119 | set(MBEDTLS_LIBRARIES ${MBEDTLS_LIBRARY} ${MBEDTLS_X509_LIBRARY} ${MBEDTLS_CRYPTO_LIBRARY}) 120 | 121 | if(MBEDTLS_INCLUDE_DIR) 122 | set(MBEDTLS_FOUND TRUE) 123 | endif() 124 | 125 | 126 | include(FindPackageHandleStandardArgs) 127 | find_package_handle_standard_args(MbedTLS 128 | FOUND_VAR MBEDTLS_FOUND 129 | REQUIRED_VARS 130 | MBEDTLS_INCLUDE_DIR 131 | MBEDTLS_LIBRARY 132 | MBEDTLS_CRYPTO_LIBRARY 133 | MBEDTLS_X509_LIBRARY 134 | MBEDTLS_LIBRARIES 135 | MBEDTLS_VERSION 136 | VERSION_VAR MBEDTLS_VERSION) 137 | 138 | 139 | if(NOT TARGET mbedtls) 140 | add_library(mbedtls UNKNOWN IMPORTED) 141 | set_target_properties(mbedtls PROPERTIES 142 | INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" 143 | IMPORTED_LINK_INTERFACE_LANGUAGES "C" 144 | IMPORTED_LOCATION "${MBEDTLS_LIBRARY}") 145 | endif() 146 | 147 | if(NOT TARGET mbedcrypto) 148 | add_library(mbedcrypto UNKNOWN IMPORTED) 149 | set_target_properties(mbedcrypto PROPERTIES 150 | IMPORTED_LINK_INTERFACE_LANGUAGES "C" 151 | IMPORTED_LOCATION "${MBEDTLS_CRYPTO_LIBRARY}") 152 | endif() 153 | 154 | if(NOT TARGET mbedx509) 155 | add_library(mbedx509 UNKNOWN IMPORTED) 156 | set_target_properties(mbedx509 PROPERTIES 157 | IMPORTED_LINK_INTERFACE_LANGUAGES "C" 158 | IMPORTED_LOCATION "${MBEDTLS_X509_LIBRARY}") 159 | endif() 160 | -------------------------------------------------------------------------------- /include/pegasocks/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_CONFIG_H 2 | #define _PGS_CONFIG_H 3 | 4 | #include "log.h" 5 | #include "crypto.h" 6 | #include "utils.h" 7 | 8 | #include 9 | #include 10 | #include "parson/parson.h" 11 | 12 | #define SERVER_TYPE_TROJAN "trojan" 13 | #define SERVER_TYPE_V2RAY "v2ray" 14 | #define SERVER_TYPE_SHADOWSOCKS "shadowsocks" 15 | 16 | #define IS_TROJAN_SERVER(type) (strcasecmp((type), SERVER_TYPE_TROJAN) == 0) 17 | #define IS_V2RAY_SERVER(type) (strcasecmp((type), SERVER_TYPE_V2RAY) == 0) 18 | #define IS_SHADOWSOCKS_SERVER(type) \ 19 | (strcasecmp((type), SERVER_TYPE_SHADOWSOCKS) == 0) 20 | #define GET_TROJAN_SNI(sconf, sni) \ 21 | do { \ 22 | sni = (sconf)->server_address; \ 23 | pgs_config_extra_trojan_t *tconf = \ 24 | (pgs_config_extra_trojan_t *)(sconf)->extra; \ 25 | if (tconf->ssl.sni != NULL) { \ 26 | sni = tconf->ssl.sni; \ 27 | } \ 28 | } while (0) 29 | 30 | #define GET_V2RAY_SNI(sconf, sni) \ 31 | do { \ 32 | pgs_config_extra_v2ray_t *vconf = \ 33 | (pgs_config_extra_v2ray_t *)(sconf)->extra; \ 34 | sni = (sconf)->server_address; \ 35 | if (vconf->ssl.enabled) { \ 36 | if (vconf->ssl.sni != NULL) { \ 37 | sni = vconf->ssl.sni; \ 38 | } \ 39 | } \ 40 | } while (0) 41 | 42 | // root config fields 43 | #define CONFIG_ACL_FILE "acl_file" 44 | #define CONFIG_LOCAL_ADDRESS "local_address" 45 | #define CONFIG_LOCAL_PORT "local_port" 46 | #define CONFIG_CONTROL_PORT "control_port" 47 | #define CONFIG_CONTROL_FILE "control_file" 48 | #define CONFIG_PING_INTERVAL "ping_interval" 49 | #define CONFIG_LOG_FILE "log_file" 50 | #define CONFIG_LOG_LEVEL "log_level" 51 | #define CONFIG_TIMEOUT "timeout" 52 | #define CONFIG_SERVERS "servers" 53 | #define CONFIG_DNS_SERVERS "dns_servers" 54 | #define CONFIG_SSL_CERT "ssl.cert" 55 | #define CONFIG_SSL_VERIFY "ssl.verify" 56 | #define CONFIG_ANDROID_PROTECT_ADDRESS "android.protect_address" 57 | #define CONFIG_ANDROID_PROTECT_PORT "android.protect_port" 58 | 59 | // server fields 60 | #define CONFIG_SERVER_ADDRESS "server_address" 61 | #define CONFIG_SERVER_TYPE "server_type" 62 | #define CONFIG_SERVER_PORT "server_port" 63 | #define CONFIG_SERVER_PASSWORD "password" 64 | 65 | // SNI 66 | #define CONFIG_SSL_SNI "ssl.sni" 67 | 68 | // WS 69 | #define CONFIG_WS_PATH "websocket.path" 70 | #define CONFIG_WS_HOSTNAME "websocket.hostname" 71 | 72 | // VMESS secure 73 | #define CONFIG_VMESS_SECURE "secure" 74 | 75 | // SHADOWSOCKS 76 | #define CONFIG_SS_METHOD "method" 77 | #define CONFIG_SS_PLUGIN "plugin" 78 | #define CONFIG_SS_PLUGIN_OPTS "plugin_opts" 79 | 80 | typedef struct pgs_config_ssl_s pgs_trojanserver_ssl_t; 81 | typedef struct pgs_config_ssl_s pgs_v2rayserver_ssl_t; 82 | 83 | #define pgs_config_info(config, ...) \ 84 | pgs_logger_main_info(config->log_file, __VA_ARGS__) 85 | #define pgs_config_error(config, ...) \ 86 | pgs_logger_main_error(config->log_file, __VA_ARGS__) 87 | #define pgs_config_warn(config, ...) \ 88 | pgs_logger_main_warn(config->log_file, __VA_ARGS__) 89 | 90 | typedef struct pgs_server_config_s { 91 | const char *server_address; 92 | const char *server_type; 93 | int server_port; 94 | uint8_t *password; 95 | void *extra; // type specific 96 | } pgs_server_config_t; 97 | 98 | typedef struct pgs_config_s { 99 | JSON_Value *root_value; 100 | const char *acl_file; 101 | pgs_server_config_t *servers; 102 | int servers_count; 103 | const char *local_address; 104 | int local_port; 105 | int control_port; 106 | const char *control_file; 107 | int timeout; 108 | int ping_interval; 109 | int log_level; 110 | FILE *log_file; 111 | bool log_isatty; 112 | const char *ssl_crt; 113 | bool ssl_verify; 114 | pgs_list_t *dns_servers; 115 | const char *android_protect_address; 116 | int android_protect_port; 117 | } pgs_config_t; 118 | 119 | typedef struct pgs_config_ssl_s { 120 | bool enabled; 121 | const char *sni; 122 | } pgs_config_ssl_t; 123 | 124 | typedef struct pgs_config_ws_s { 125 | bool enabled; 126 | const char *path; 127 | const char *hostname; 128 | } pgs_config_ws_t; 129 | 130 | typedef struct pgs_config_extra_trojan_s { 131 | pgs_config_ws_t websocket; 132 | pgs_config_ssl_t ssl; 133 | } pgs_config_extra_trojan_t; 134 | 135 | typedef struct pgs_config_extra_v2ray_s { 136 | pgs_config_ws_t websocket; 137 | pgs_config_ssl_t ssl; 138 | pgs_cryptor_type_t secure; 139 | } pgs_config_extra_v2ray_t; 140 | 141 | typedef struct pgs_config_extra_ss_s { 142 | pgs_cryptor_type_t method; 143 | const char *plugin; 144 | const char *plugin_opts; 145 | } pgs_config_extra_ss_t; 146 | 147 | /* common */ 148 | // load config from file 149 | pgs_config_t *pgs_config_load(const char *config); 150 | // parse config from string 151 | pgs_config_t *pgs_config_parse(const char *json); 152 | pgs_server_config_t *pgs_config_parse_servers(pgs_config_t *config, 153 | JSON_Array *arr); 154 | pgs_config_t *pgs_config_new(); 155 | void pgs_config_free(pgs_config_t *config); 156 | pgs_server_config_t *pgs_servers_config_new(uint64_t len); 157 | void pgs_servers_config_free(pgs_server_config_t *servers, 158 | uint64_t servers_count); 159 | void *pgs_server_config_parse_extra(pgs_config_t *config, 160 | const char *server_type, JSON_Object *jobj); 161 | void pgs_server_config_free_extra(const char *server_type, void *ptr); 162 | 163 | /* trojan config */ 164 | pgs_config_extra_trojan_t *pgs_config_extra_trojan_parse(pgs_config_t *config, 165 | JSON_Object *jobj); 166 | pgs_config_extra_trojan_t *pgs_config_extra_trojan_new(); 167 | void pgs_config_extra_trojan_free(pgs_config_extra_trojan_t *tconf); 168 | 169 | /* v2ray config */ 170 | pgs_config_extra_v2ray_t *pgs_config_extra_v2ray_parse(pgs_config_t *config, 171 | JSON_Object *jobj); 172 | pgs_config_extra_v2ray_t *pgs_config_extra_v2ray_new(); 173 | void pgs_config_extra_v2ray_free(pgs_config_extra_v2ray_t *ptr); 174 | 175 | /* shadowsocks config */ 176 | pgs_config_extra_ss_t *pgs_config_extra_ss_parse(pgs_config_t *config, 177 | JSON_Object *jobj); 178 | pgs_config_extra_ss_t *pgs_config_extra_ss_new(); 179 | void pgs_config_extra_ss_free(pgs_config_extra_ss_t *ptr); 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /src/crypto/openssl.c: -------------------------------------------------------------------------------- 1 | #include "crypto.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static const EVP_CIPHER *get_openssl_cipher(pgs_cryptor_type_t cipher) 11 | { 12 | switch (cipher) { 13 | case AES_128_CFB: 14 | return EVP_aes_128_cfb(); 15 | case AEAD_AES_128_GCM: 16 | return EVP_aes_128_gcm(); 17 | case AEAD_AES_256_GCM: 18 | return EVP_aes_256_gcm(); 19 | case AEAD_CHACHA20_POLY1305: 20 | return EVP_chacha20_poly1305(); 21 | default: 22 | break; 23 | } 24 | } 25 | 26 | pgs_cryptor_t *pgs_cryptor_new(pgs_cryptor_type_t cipher, 27 | pgs_cryptor_direction_t dir, const uint8_t *key, 28 | const uint8_t *iv) 29 | { 30 | pgs_cryptor_t *ptr = malloc(sizeof(pgs_cryptor_t)); 31 | ptr->cipher = cipher; 32 | ptr->dir = dir; 33 | ptr->key = key; 34 | ptr->iv = iv; 35 | 36 | if (!(ptr->ctx = EVP_CIPHER_CTX_new())) 37 | goto error; 38 | 39 | pgs_cryptor_type_info(cipher, &ptr->key_len, &ptr->iv_len, 40 | &ptr->tag_len); 41 | 42 | const EVP_CIPHER *openssl_cipher = get_openssl_cipher(cipher); 43 | 44 | if (openssl_cipher == NULL) 45 | goto error; 46 | 47 | switch (dir) { 48 | case PGS_ENCRYPT: { 49 | if (1 != 50 | EVP_EncryptInit_ex(ptr->ctx, openssl_cipher, NULL, key, iv)) 51 | goto error; 52 | break; 53 | } 54 | case PGS_DECRYPT: { 55 | if (1 != 56 | EVP_DecryptInit_ex(ptr->ctx, openssl_cipher, NULL, key, iv)) 57 | goto error; 58 | break; 59 | } 60 | default: 61 | goto error; 62 | } 63 | 64 | return ptr; 65 | 66 | error: 67 | pgs_cryptor_free(ptr); 68 | return NULL; 69 | } 70 | 71 | void pgs_cryptor_free(pgs_cryptor_t *ptr) 72 | { 73 | if (ptr->ctx) { 74 | EVP_CIPHER_CTX_free(ptr->ctx); 75 | } 76 | free(ptr); 77 | ptr = NULL; 78 | } 79 | 80 | bool pgs_cryptor_encrypt(pgs_cryptor_t *ptr, const uint8_t *plaintext, 81 | size_t plaintext_len, uint8_t *tag, 82 | uint8_t *ciphertext, size_t *ciphertext_len) 83 | { 84 | int len; 85 | if (1 != EVP_EncryptUpdate(ptr->ctx, ciphertext, &len, plaintext, 86 | plaintext_len)) 87 | return false; 88 | 89 | *ciphertext_len = len; 90 | 91 | if (is_aead_cryptor(ptr)) { 92 | if (1 != EVP_EncryptFinal_ex(ptr->ctx, ciphertext + len, &len)) 93 | return false; 94 | *ciphertext_len += len; 95 | 96 | if (1 != EVP_CIPHER_CTX_ctrl(ptr->ctx, EVP_CTRL_AEAD_GET_TAG, 97 | ptr->tag_len, tag)) 98 | return false; 99 | } 100 | 101 | return true; 102 | } 103 | 104 | bool pgs_cryptor_decrypt(pgs_cryptor_t *ptr, const uint8_t *ciphertext, 105 | size_t ciphertext_len, const uint8_t *tag, 106 | uint8_t *plaintext, size_t *plaintext_len) 107 | { 108 | int len = 0; 109 | if (!EVP_DecryptUpdate(ptr->ctx, plaintext, &len, ciphertext, 110 | ciphertext_len)) 111 | return false; 112 | *plaintext_len = len; 113 | 114 | if (is_aead_cryptor(ptr)) { 115 | if (!EVP_CIPHER_CTX_ctrl(ptr->ctx, EVP_CTRL_AEAD_SET_TAG, 116 | ptr->tag_len, (void *)tag)) 117 | return false; 118 | 119 | if (!EVP_DecryptFinal_ex(ptr->ctx, plaintext + len, &len)) 120 | return false; 121 | 122 | *plaintext_len += len; 123 | } 124 | return true; 125 | } 126 | 127 | void pgs_cryptor_reset_iv(pgs_cryptor_t *ptr, const uint8_t *iv) 128 | { 129 | const EVP_CIPHER *openssl_cipher = get_openssl_cipher(ptr->cipher); 130 | EVP_CIPHER_CTX_reset(ptr->ctx); 131 | ptr->iv = iv; 132 | switch (ptr->dir) { 133 | case PGS_ENCRYPT: { 134 | EVP_EncryptInit_ex(ptr->ctx, openssl_cipher, NULL, ptr->key, 135 | ptr->iv); 136 | break; 137 | } 138 | case PGS_DECRYPT: { 139 | EVP_DecryptInit_ex(ptr->ctx, openssl_cipher, NULL, ptr->key, 140 | ptr->iv); 141 | break; 142 | } 143 | default: 144 | break; 145 | } 146 | } 147 | 148 | // helpers 149 | // returns 1 on success 150 | int rand_bytes(unsigned char *buf, int num) 151 | { 152 | return RAND_bytes(buf, num); 153 | } 154 | 155 | void sha224(const uint8_t *input, uint64_t input_len, uint8_t *res, 156 | uint64_t *res_len) 157 | { 158 | EVP_MD_CTX *ctx; 159 | if ((ctx = EVP_MD_CTX_new()) == NULL) 160 | goto error; 161 | if (!EVP_DigestInit_ex(ctx, EVP_sha224(), NULL)) 162 | goto error; 163 | if (!EVP_DigestUpdate(ctx, input, input_len)) 164 | goto error; 165 | if (!EVP_DigestFinal_ex(ctx, res, (unsigned int *)res_len)) 166 | goto error; 167 | 168 | EVP_MD_CTX_free(ctx); 169 | return; 170 | 171 | error: 172 | perror("error sha224"); 173 | if (ctx != NULL) 174 | EVP_MD_CTX_free(ctx); 175 | *res_len = 0; 176 | } 177 | 178 | void md5(const uint8_t *input, uint64_t input_len, uint8_t *res) 179 | { 180 | MD5(input, input_len, res); 181 | } 182 | 183 | void sha1(const uint8_t *input, uint64_t input_len, uint8_t *res) 184 | { 185 | SHA1(input, input_len, res); 186 | } 187 | 188 | void hmac_md5(const uint8_t *key, uint64_t key_len, const uint8_t *data, 189 | uint64_t data_len, uint8_t *out, uint64_t *out_len) 190 | { 191 | HMAC(EVP_md5(), key, key_len, data, data_len, out, 192 | (unsigned int *)out_len); 193 | assert(*out_len == 16); 194 | } 195 | 196 | int aes_128_cfb_encrypt(const uint8_t *plaintext, int plaintext_len, 197 | const uint8_t *key, const uint8_t *iv, 198 | uint8_t *ciphertext) 199 | { 200 | int ciphertext_len; 201 | int len; 202 | EVP_CIPHER_CTX *ctx; 203 | if (!(ctx = EVP_CIPHER_CTX_new())) 204 | goto error; 205 | 206 | if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cfb(), NULL, key, iv)) 207 | goto error; 208 | 209 | if (1 != 210 | EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) 211 | goto error; 212 | ciphertext_len = len; 213 | 214 | if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) 215 | goto error; 216 | ciphertext_len += len; 217 | 218 | EVP_CIPHER_CTX_free(ctx); 219 | 220 | return ciphertext_len; 221 | 222 | error: 223 | perror("aes_128_cfb_encrypt"); 224 | return -1; 225 | } 226 | 227 | int aes_128_cfb_decrypt(const uint8_t *ciphertext, int ciphertext_len, 228 | const uint8_t *key, const uint8_t *iv, 229 | uint8_t *plaintext) 230 | { 231 | int plaintext_len; 232 | 233 | int len; 234 | EVP_CIPHER_CTX *ctx; 235 | if (!(ctx = EVP_CIPHER_CTX_new())) 236 | goto error; 237 | 238 | if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cfb(), NULL, key, iv)) 239 | goto error; 240 | 241 | if (1 != 242 | EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) 243 | goto error; 244 | plaintext_len = len; 245 | 246 | if (1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) 247 | goto error; 248 | plaintext_len += len; 249 | 250 | EVP_CIPHER_CTX_free(ctx); 251 | 252 | return plaintext_len; 253 | 254 | error: 255 | perror("aes_128_cfb_decrypt"); 256 | return -1; 257 | } 258 | 259 | bool hkdf_sha1(const uint8_t *salt, size_t salt_len, const uint8_t *ikm, 260 | size_t ikm_len, const uint8_t *info, size_t info_len, 261 | uint8_t *okm, size_t okm_len) 262 | 263 | { 264 | size_t outlen = okm_len; 265 | EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); 266 | 267 | if (EVP_PKEY_derive_init(pctx) <= 0) 268 | goto error; 269 | if (EVP_PKEY_CTX_hkdf_mode(pctx, 270 | EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) <= 0) 271 | goto error; 272 | if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha1()) <= 0) 273 | goto error; 274 | if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) 275 | goto error; 276 | if (EVP_PKEY_CTX_set1_hkdf_key(pctx, ikm, ikm_len) <= 0) 277 | goto error; 278 | if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) 279 | goto error; 280 | if (EVP_PKEY_derive(pctx, okm, &outlen) <= 0) 281 | goto error; 282 | 283 | assert(outlen == okm_len); 284 | EVP_PKEY_CTX_free(pctx); 285 | return true; 286 | error: 287 | return false; 288 | } 289 | -------------------------------------------------------------------------------- /include/pegasocks/session/outbound.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_OUTBOUND_H 2 | #define _PGS_OUTBOUND_H 3 | 4 | #include "acl.h" 5 | #include "server/local.h" 6 | #include "config.h" 7 | #include "crypto.h" 8 | #include "ssl.h" 9 | #include "utils.h" 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #ifdef USE_MBEDTLS 18 | #include 19 | #else 20 | #include 21 | #endif 22 | 23 | #define PGS_OUTBOUND_SET_READ_TIMEOUT(outbound, sec) \ 24 | do { \ 25 | struct timeval tv; \ 26 | tv.tv_sec = sec; \ 27 | tv.tv_usec = 0; \ 28 | bufferevent_set_timeouts((outbound->bev), &tv, NULL); \ 29 | } while (0) 30 | 31 | typedef struct pgs_session_outbound_s pgs_session_outbound_t; 32 | typedef struct pgs_outbound_init_param_s { 33 | bool proxy; 34 | bool is_udp; 35 | 36 | pgs_logger_t *logger; 37 | pgs_local_server_t *local; 38 | pgs_session_outbound_t *outbound; 39 | 40 | const pgs_config_t *gconfig; 41 | const pgs_server_config_t *config; 42 | 43 | const uint8_t *cmd; 44 | size_t cmd_len; 45 | 46 | /* for remote read callback (pgs_session_t*) */ 47 | void *cb_ctx; 48 | } pgs_outbound_init_param_t; 49 | 50 | struct pgs_session_outbound_s { 51 | bool ready; 52 | bool bypass; 53 | 54 | struct bufferevent *bev; 55 | const pgs_server_config_t *config; 56 | char *dest; 57 | int port; 58 | 59 | #ifdef WITH_ACL 60 | /* used to update the inner reference of outbound, need when enabled ACL */ 61 | pgs_outbound_init_param_t *param; 62 | struct evdns_base *dns_base; 63 | struct evdns_request *dns_req; 64 | #endif 65 | 66 | void *ctx; 67 | }; 68 | 69 | typedef struct pgs_outbound_ctx_trojan_s { 70 | // sha224(password) + "\r\n" + cmd[1] + cmd.substr(3) + "\r\n" 71 | char *head; 72 | size_t head_len; 73 | } pgs_outbound_ctx_trojan_t; 74 | 75 | typedef struct pgs_outbound_ctx_v2ray_s { 76 | // key and iv for command part 77 | uint8_t iv[AES_128_CFB_IV_LEN]; 78 | uint8_t key[AES_128_CFB_KEY_LEN]; 79 | uint8_t riv[AES_128_CFB_IV_LEN]; 80 | uint8_t rkey[AES_128_CFB_KEY_LEN]; 81 | 82 | pgs_buffer_t *lrbuf; 83 | pgs_buffer_t *lwbuf; 84 | pgs_buffer_t *rrbuf; 85 | pgs_buffer_t *rwbuf; 86 | 87 | uint8_t target_addr[BUFSIZE_512]; /*atype(1) | addr | port(2)*/ 88 | 89 | // for request header 90 | const uint8_t *cmd; 91 | size_t cmdlen; 92 | bool header_sent; 93 | uint8_t v; 94 | 95 | // for resp header 96 | bool header_recved; 97 | size_t resp_len; 98 | size_t target_addr_len; 99 | size_t remote_rbuf_pos; 100 | uint32_t resp_hash; 101 | 102 | // key and iv for data part 103 | uint8_t *data_enc_iv; 104 | uint8_t *data_enc_key; 105 | uint8_t *data_dec_iv; 106 | uint8_t *data_dec_key; 107 | size_t key_len; 108 | size_t iv_len; 109 | size_t tag_len; 110 | uint16_t enc_counter; 111 | uint16_t dec_counter; 112 | 113 | pgs_cryptor_t *encryptor; 114 | pgs_cryptor_t *decryptor; 115 | pgs_cryptor_type_t cipher; 116 | } pgs_outbound_ctx_v2ray_t; 117 | 118 | typedef struct pgs_outbound_ctx_ss_s { 119 | pgs_buffer_t *rbuf; 120 | pgs_buffer_t *wbuf; 121 | 122 | const uint8_t *cmd; 123 | size_t cmd_len; 124 | 125 | bool iv_sent; 126 | 127 | /* AEAD decode state machine */ 128 | enum { 129 | READY = 0, 130 | WAIT_MORE_FOR_LEN, /* len(data) < 2 + tag_len */ 131 | WAIT_MORE_FOR_PAYLOAD, /* len(data) < 2 + tag_len + payload_len + tag_len */ 132 | } aead_decode_state; 133 | size_t plen; 134 | 135 | /* salt + ikm(pass) => encode key; len(salt) = len(key) */ 136 | uint8_t *enc_key; /* random bytes, to send */ 137 | uint8_t *enc_iv; 138 | uint8_t *dec_key; /* to receive by salt (AEAD) */ 139 | uint8_t *dec_iv; /* to receive(AES) */ 140 | uint8_t *ikm; 141 | uint8_t *enc_salt; 142 | size_t key_len; 143 | size_t iv_len; 144 | size_t tag_len; 145 | pgs_cryptor_t *encryptor; 146 | pgs_cryptor_t *decryptor; 147 | pgs_cryptor_type_t cipher; 148 | } pgs_outbound_ctx_ss_t; 149 | 150 | void socks5_dest_addr_parse(const uint8_t *cmd, size_t cmd_len, uint8_t *atype, 151 | char **dest_ptr, int *port); 152 | 153 | // trojan session context 154 | pgs_outbound_ctx_trojan_t * 155 | pgs_outbound_ctx_trojan_new(const uint8_t *encodepass, size_t passlen, 156 | const uint8_t *cmd, size_t cmdlen); 157 | 158 | void pgs_outbound_ctx_trojan_free(pgs_outbound_ctx_trojan_t *ctx); 159 | 160 | // vmess context 161 | pgs_outbound_ctx_v2ray_t *pgs_outbound_ctx_v2ray_new(const uint8_t *cmd, 162 | size_t cmdlen, 163 | pgs_cryptor_type_t cipher); 164 | void pgs_outbound_ctx_v2ray_free(pgs_outbound_ctx_v2ray_t *ptr); 165 | 166 | // shadowsocks context 167 | pgs_outbound_ctx_ss_t *pgs_outbound_ctx_ss_new(const uint8_t *cmd, 168 | size_t cmd_len, 169 | const uint8_t *password, 170 | size_t password_len, 171 | pgs_cryptor_type_t cipher); 172 | void pgs_outbound_ctx_ss_free(pgs_outbound_ctx_ss_t *ptr); 173 | 174 | // outbound 175 | void pgs_session_outbound_free(pgs_session_outbound_t *ptr); 176 | 177 | bool pgs_session_trojan_outbound_init( 178 | pgs_session_outbound_t *ptr, pgs_logger_t *logger, 179 | const pgs_config_t *gconfig, const pgs_server_config_t *config, 180 | const uint8_t *cmd, size_t cmd_len, struct event_base *base, 181 | pgs_ssl_ctx_t *ssl_ctx, on_event_cb *event_cb, on_read_cb *read_cb, 182 | void *cb_ctx); 183 | 184 | bool pgs_session_v2ray_outbound_init( 185 | pgs_session_outbound_t *ptr, pgs_logger_t *logger, 186 | const pgs_config_t *gconfig, const pgs_server_config_t *config, 187 | const uint8_t *cmd, size_t cmd_len, struct event_base *base, 188 | pgs_ssl_ctx_t *ssl_ctx, on_event_cb *event_cb, on_read_cb *read_cb, 189 | void *cb_ctx); 190 | 191 | bool pgs_session_ss_outbound_init( 192 | pgs_session_outbound_t *ptr, pgs_logger_t *logger, 193 | const pgs_config_t *gconfig, const pgs_server_config_t *config, 194 | const uint8_t *cmd, size_t cmd_len, struct event_base *base, 195 | on_event_cb *event_cb, on_read_cb *read_cb, void *cb_ctx); 196 | 197 | bool pgs_session_bypass_outbound_init(pgs_session_outbound_t *ptr, 198 | pgs_logger_t *logger, 199 | const pgs_config_t *gconfig, 200 | struct event_base *base, 201 | on_event_cb *event_cb, 202 | on_read_cb *read_cb, void *cb_ctx); 203 | 204 | pgs_session_outbound_t *pgs_session_outbound_new(); 205 | 206 | bool pgs_session_outbound_init(pgs_session_outbound_t *ptr, bool is_udp, 207 | pgs_logger_t *logger, 208 | const pgs_config_t *gconfig, 209 | const pgs_server_config_t *config, 210 | const uint8_t *cmd, size_t cmd_len, 211 | pgs_local_server_t *local, void *cb_ctx); 212 | 213 | static inline bool 214 | pgs_session_outbound_is_ssl(const pgs_session_outbound_t *ptr) 215 | { 216 | bool is_be_ssl = false; 217 | const pgs_server_config_t *config = ptr->config; 218 | 219 | if (IS_V2RAY_SERVER(config->server_type)) { 220 | pgs_config_extra_v2ray_t *vconf = 221 | (pgs_config_extra_v2ray_t *)config->extra; 222 | if (vconf->ssl.enabled) { 223 | is_be_ssl = true; 224 | } 225 | } 226 | if (IS_TROJAN_SERVER(config->server_type)) { 227 | is_be_ssl = true; 228 | } 229 | return is_be_ssl; 230 | } 231 | 232 | static inline bool 233 | pgs_session_outbound_is_ssl_reused(const pgs_session_outbound_t *ptr) 234 | { 235 | if (!pgs_session_outbound_is_ssl(ptr) || ptr->bypass) 236 | return false; 237 | bool reused = false; 238 | 239 | #ifdef USE_MBEDTLS 240 | reused = false; 241 | #else 242 | if (ptr->bev) { 243 | SSL *ssl = bufferevent_openssl_get_ssl(ptr->bev); 244 | reused = SSL_session_reused(ssl); 245 | } 246 | #endif 247 | 248 | return reused; 249 | } 250 | 251 | #endif 252 | -------------------------------------------------------------------------------- /src/acl.c: -------------------------------------------------------------------------------- 1 | #include "acl.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define LINE_BUFF_SIZE 256 13 | 14 | typedef struct pgs_inner_acl_s { 15 | pgs_acl_mode mode; 16 | struct ip_set v4set; 17 | struct ip_set v6set; 18 | struct cork_dllist rules; 19 | } pgs_inner_acl_t; 20 | 21 | /* store two types of acl 22 | * if domain in bypass_acl/proxy_acl, directly bypass/proxy without DNS resolve. 23 | * Otherwise we query DNS, then match again */ 24 | struct pgs_acl_s { 25 | /* proxy all, bypass list */ 26 | pgs_inner_acl_t *bypass_acl; 27 | /* bypass all, proxy list */ 28 | pgs_inner_acl_t *proxy_acl; 29 | }; 30 | 31 | struct pgs_acl_rule_s { 32 | char *raw; 33 | pcre *pattern; 34 | struct cork_dllist_item entry; 35 | }; 36 | 37 | static void pgs_acl_add_rule(pgs_inner_acl_t *acl, const char *raw) 38 | { 39 | pgs_acl_rule_t *rule = pgs_acl_rule_new(raw); 40 | cork_dllist_add(&acl->rules, &rule->entry); 41 | } 42 | 43 | static pgs_acl_mode pgs_acl_get_mode(pgs_inner_acl_t *ptr) 44 | { 45 | return ptr->mode; 46 | } 47 | 48 | static void parse_addr_cidr(const char *str, char *host, int *cidr) 49 | { 50 | int ret = -1; 51 | char *pch; 52 | 53 | pch = strchr(str, '/'); 54 | while (pch != NULL) { 55 | ret = pch - str; 56 | pch = strchr(pch + 1, '/'); 57 | } 58 | if (ret == -1) { 59 | strcpy(host, str); 60 | *cidr = -1; 61 | } else { 62 | memcpy(host, str, ret); 63 | host[ret] = '\0'; 64 | *cidr = atoi(str + ret + 1); 65 | } 66 | } 67 | 68 | static char *trimwhitespace(char *str) 69 | { 70 | char *end; 71 | 72 | while (isspace((unsigned char)*str)) 73 | str++; 74 | 75 | if (*str == 0) 76 | return str; 77 | 78 | end = str + strlen(str) - 1; 79 | while (end > str && isspace((unsigned char)*end)) 80 | end--; 81 | 82 | *(end + 1) = 0; 83 | 84 | return str; 85 | } 86 | 87 | pgs_acl_t *pgs_acl_new(const char *path) 88 | { 89 | pgs_acl_t *ptr = malloc(sizeof(pgs_acl_t)); 90 | ptr->bypass_acl = malloc(sizeof(pgs_inner_acl_t)); 91 | ptr->proxy_acl = malloc(sizeof(pgs_inner_acl_t)); 92 | 93 | ptr->proxy_acl->mode = BYPASS_ALL_PROXY_LIST; 94 | ptr->bypass_acl->mode = PROXY_ALL_BYPASS_LIST; 95 | 96 | ipset_init(&ptr->proxy_acl->v4set); 97 | ipset_init(&ptr->proxy_acl->v6set); 98 | cork_dllist_init(&ptr->proxy_acl->rules); 99 | 100 | ipset_init(&ptr->bypass_acl->v4set); 101 | ipset_init(&ptr->bypass_acl->v6set); 102 | cork_dllist_init(&ptr->bypass_acl->rules); 103 | 104 | FILE *fd = fopen(path, "r"); 105 | if (fd == NULL) 106 | goto error; 107 | 108 | char buff[LINE_BUFF_SIZE]; 109 | 110 | pgs_inner_acl_t *acl = NULL; 111 | 112 | // parsing logic is modifed from 113 | // https://github.com/shadowsocks/shadowsocks-libev/blob/master/src/acl.c#L133 114 | while (!feof(fd)) { 115 | if (fgets(buff, LINE_BUFF_SIZE, fd)) { 116 | // Discards the whole line if longer than 255 characters 117 | int long_line = 0; // 1: Long 2: Error 118 | while ((strlen(buff) == 255) && (buff[254] != '\n')) { 119 | long_line = 1; 120 | if (fgets(buff, LINE_BUFF_SIZE, fd) == NULL) { 121 | long_line = 2; 122 | break; 123 | } 124 | } 125 | if (long_line) { 126 | continue; 127 | } 128 | 129 | // Trim the newline 130 | int len = strlen(buff); 131 | if (len > 0 && buff[len - 1] == '\n') { 132 | buff[len - 1] = '\0'; 133 | } 134 | 135 | char *comment = strchr(buff, '#'); 136 | if (comment) { 137 | *comment = '\0'; 138 | } 139 | 140 | char *line = trimwhitespace(buff); 141 | if (strlen(line) == 0) { 142 | continue; 143 | } 144 | 145 | // notice: outbound_block_list is not supported 146 | if (strcmp(line, "[black_list]") == 0 || 147 | strcmp(line, "[bypass_list]") == 0) { 148 | acl = ptr->bypass_acl; 149 | // ptr->mode = PROXY_ALL_BYPASS_LIST; 150 | continue; 151 | } else if (strcmp(line, "[accept_all]") == 0 || 152 | strcmp(line, "[proxy_all]") == 0) { 153 | acl = ptr->bypass_acl; 154 | // ptr->mode = PROXY_ALL_BYPASS_LIST; 155 | continue; 156 | } else if (strcmp(line, "[white_list]") == 0 || 157 | strcmp(line, "[proxy_list]") == 0) { 158 | acl = ptr->proxy_acl; 159 | // ptr->mode = BYPASS_ALL_PROXY_LIST; 160 | continue; 161 | } else if (strcmp(line, "[reject_all]") == 0 || 162 | strcmp(line, "[bypass_all]") == 0) { 163 | acl = ptr->proxy_acl; 164 | // ptr->mode = BYPASS_ALL_PROXY_LIST; 165 | continue; 166 | } 167 | 168 | char host[LINE_BUFF_SIZE]; 169 | int cidr; 170 | parse_addr_cidr(line, host, &cidr); 171 | 172 | struct cork_ip addr; 173 | int err = cork_ip_init(&addr, host); 174 | if (!err) { 175 | if (addr.version == 4) { 176 | if (cidr >= 0) { 177 | ipset_ipv4_add_network( 178 | &acl->v4set, 179 | &(addr.ip.v4), cidr); 180 | } else { 181 | ipset_ipv4_add(&acl->v4set, 182 | &(addr.ip.v4)); 183 | } 184 | } else if (addr.version == 6) { 185 | if (cidr >= 0) { 186 | ipset_ipv6_add_network( 187 | &acl->v6set, 188 | &(addr.ip.v6), cidr); 189 | } else { 190 | ipset_ipv6_add(&acl->v6set, 191 | &(addr.ip.v6)); 192 | } 193 | } 194 | } else { 195 | pgs_acl_add_rule(acl, line); 196 | } 197 | } 198 | } 199 | 200 | fclose(fd); 201 | 202 | return ptr; 203 | 204 | error: 205 | fclose(fd); 206 | pgs_acl_free(ptr); 207 | return NULL; 208 | } 209 | 210 | static void pgs_inner_acl_free(pgs_inner_acl_t *ptr) 211 | { 212 | ipset_done(&ptr->v4set); 213 | ipset_done(&ptr->v6set); 214 | 215 | struct cork_dllist_item *iter; 216 | while ((iter = cork_dllist_head(&ptr->rules)) != NULL) { 217 | pgs_acl_rule_t *rule = 218 | cork_container_of(iter, pgs_acl_rule_t, entry); 219 | cork_dllist_remove(&rule->entry); 220 | pgs_acl_rule_free(rule); 221 | } 222 | free(ptr); 223 | ptr = NULL; 224 | } 225 | 226 | void pgs_acl_free(pgs_acl_t *ptr) 227 | { 228 | if (ptr->bypass_acl) 229 | pgs_inner_acl_free(ptr->bypass_acl); 230 | if (ptr->proxy_acl) 231 | pgs_inner_acl_free(ptr->proxy_acl); 232 | if (ptr) 233 | free(ptr); 234 | } 235 | 236 | pgs_acl_rule_t *pgs_acl_rule_new(const char *raw) 237 | { 238 | pgs_acl_rule_t *ptr = calloc(1, sizeof(pgs_acl_rule_t)); 239 | ptr->raw = strdup(raw); 240 | 241 | const char *reerr; 242 | int reerroffset; 243 | ptr->pattern = pcre_compile(ptr->raw, 0, &reerr, &reerroffset, NULL); 244 | if (ptr->pattern == NULL) 245 | goto error; 246 | return ptr; 247 | 248 | error: 249 | pgs_acl_rule_free(ptr); 250 | return NULL; 251 | } 252 | 253 | void pgs_acl_rule_free(pgs_acl_rule_t *ptr) 254 | { 255 | if (ptr->pattern) 256 | pcre_free(ptr->pattern); 257 | free(ptr->raw); 258 | free(ptr); 259 | ptr = NULL; 260 | } 261 | 262 | static bool pgs_inner_acl_match_host(pgs_inner_acl_t *acl, const char *host) 263 | { 264 | struct cork_ip addr; 265 | int err = cork_ip_init(&addr, host); 266 | 267 | if (err) { 268 | int host_len = strlen(host); 269 | 270 | struct cork_dllist_item *curr, *next; 271 | pgs_acl_rule_t *rule; 272 | cork_dllist_foreach(&acl->rules, curr, next, pgs_acl_rule_t, 273 | rule, entry) 274 | { 275 | if (pcre_exec(rule->pattern, NULL, host, host_len, 0, 0, 276 | NULL, 0) >= 0) 277 | return true; 278 | } 279 | return false; 280 | } 281 | 282 | if (addr.version == 4) { 283 | return ipset_contains_ipv4(&acl->v4set, &(addr.ip.v4)); 284 | } else if (addr.version == 6) { 285 | return ipset_contains_ipv6(&acl->v6set, &(addr.ip.v6)); 286 | } 287 | return false; 288 | } 289 | 290 | bool pgs_acl_match_host_bypass(pgs_acl_t *acl, const char *host) 291 | { 292 | /* proxy all bypass list, if match, we bypass it */ 293 | return pgs_inner_acl_match_host(acl->bypass_acl, host); 294 | } 295 | 296 | bool pgs_acl_match_host_proxy(pgs_acl_t *acl, const char *host) 297 | { 298 | /* bypass all proxy list, if match, we proxy it */ 299 | return pgs_inner_acl_match_host(acl->proxy_acl, host); 300 | } 301 | -------------------------------------------------------------------------------- /src/ssl/openssl.c: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | #include "ssl.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct pgs_ssl_sessions_cache_s { 10 | const char *sni; 11 | pgs_list_t *ssl_sessions; 12 | pthread_mutex_t lock; 13 | } pgs_ssl_sessions_cache_t; 14 | 15 | static pgs_ssl_sessions_cache_t *session_cache_new(const char *sni); 16 | static void session_cache_free(pgs_ssl_sessions_cache_t *); 17 | 18 | static int new_session_cb(SSL *ssl, SSL_SESSION *session); 19 | static void remove_session_cb(SSL_CTX *_, SSL_SESSION *session); 20 | 21 | static SSL_SESSION *get_session_from_cache(const char *sni); 22 | 23 | // each server per cache 24 | pgs_list_t *SESSION_CACHE_LIST = NULL; 25 | 26 | struct pgs_ssl_ctx_s { 27 | SSL_CTX *_; 28 | }; 29 | 30 | // default to openssl 31 | pgs_ssl_ctx_t *pgs_ssl_ctx_new(pgs_config_t *config) 32 | { 33 | pgs_ssl_ctx_t *ptr = malloc(sizeof(pgs_ssl_ctx_t)); 34 | ptr->_ = NULL; 35 | 36 | OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | 37 | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, 38 | NULL); 39 | if ((ptr->_ = SSL_CTX_new(SSLv23_client_method()))) { 40 | if (!config->ssl_verify) { 41 | SSL_CTX_set_verify(ptr->_, SSL_VERIFY_NONE, NULL); 42 | } else { 43 | // Note: this will not verify the hostname, check: https://archives.seul.org/libevent/users/Jan-2013/msg00039.html 44 | SSL_CTX_set_verify(ptr->_, SSL_VERIFY_PEER, NULL); 45 | 46 | bool cert_loaded = false; 47 | 48 | // try load from crt 49 | if (config->ssl_crt != NULL) { 50 | if (SSL_CTX_load_verify_locations( 51 | ptr->_, config->ssl_crt, NULL) != 52 | 1) { 53 | pgs_config_error( 54 | config, 55 | "Failed to load cert: %s", 56 | config->ssl_crt); 57 | } else { 58 | pgs_config_info(config, 59 | "cert: %s loaded", 60 | config->ssl_crt); 61 | cert_loaded = true; 62 | } 63 | } 64 | // try load from system 65 | if (!cert_loaded) { 66 | X509_STORE *store = 67 | SSL_CTX_get_cert_store(ptr->_); 68 | if (X509_STORE_set_default_paths(store) != 1) { 69 | pgs_config_warn( 70 | config, 71 | "Failed to load system default cert, set verify mode to SSL_VERIFY_NONE now."); 72 | SSL_CTX_set_verify( 73 | ptr->_, SSL_VERIFY_NONE, NULL); 74 | } else { 75 | pgs_config_info(config, 76 | "system cert loaded"); 77 | } 78 | } 79 | } 80 | SSL_CTX_set_mode(ptr->_, SSL_MODE_AUTO_RETRY); 81 | SSL_CTX_set_session_cache_mode(ptr->_, SSL_SESS_CACHE_OFF); 82 | if (SESSION_CACHE_LIST == NULL) { 83 | SESSION_CACHE_LIST = pgs_list_new(); 84 | SESSION_CACHE_LIST->free = (void *)session_cache_free; 85 | 86 | for (int i = 0; i < config->servers_count; ++i) { 87 | pgs_server_config_t sconfig = 88 | config->servers[i]; 89 | pgs_ssl_sessions_cache_t *cache = NULL; 90 | if (IS_TROJAN_SERVER(sconfig.server_type)) { 91 | const char *sni = NULL; 92 | GET_TROJAN_SNI(&sconfig, sni); 93 | cache = session_cache_new(sni); 94 | } else if (IS_V2RAY_SERVER( 95 | sconfig.server_type)) { 96 | pgs_config_extra_v2ray_t *vconf = 97 | (pgs_config_extra_v2ray_t *) 98 | sconfig.extra; 99 | if (vconf->ssl.enabled) { 100 | const char *sni = NULL; 101 | GET_V2RAY_SNI(&sconfig, sni); 102 | cache = session_cache_new(sni); 103 | } 104 | } 105 | if (cache != NULL) { 106 | pgs_list_add(SESSION_CACHE_LIST, 107 | pgs_list_node_new(cache)); 108 | } 109 | } 110 | } 111 | SSL_CTX_set_session_cache_mode(ptr->_, SSL_SESS_CACHE_CLIENT); 112 | SSL_CTX_sess_set_new_cb(ptr->_, new_session_cb); 113 | SSL_CTX_sess_set_remove_cb(ptr->_, remove_session_cb); 114 | } 115 | return ptr; 116 | } 117 | 118 | void pgs_ssl_ctx_free(pgs_ssl_ctx_t *ctx) 119 | { 120 | SSL_CTX_free(ctx->_); 121 | free(ctx); 122 | if (SESSION_CACHE_LIST != NULL) { 123 | pgs_list_free(SESSION_CACHE_LIST); 124 | SESSION_CACHE_LIST = NULL; 125 | } 126 | } 127 | 128 | // init bev with ssl context 129 | // return 0 for ok 130 | // return -1 for error 131 | int pgs_session_outbound_ssl_bev_init(struct bufferevent **bev, int fd, 132 | struct event_base *base, 133 | pgs_ssl_ctx_t *ssl_ctx, const char *sni) 134 | { 135 | SSL *ssl = NULL; 136 | // ssl will be freed because BEV_OPT_CLOSE_ON_FREE 137 | if ((ssl = SSL_new(ssl_ctx->_))) 138 | SSL_set_tlsext_host_name(ssl, sni); 139 | 140 | if (ssl == NULL) { 141 | return -1; 142 | } 143 | SSL_SESSION *session = get_session_from_cache(sni); 144 | if (session != NULL) { 145 | SSL_set_session(ssl, session); 146 | } 147 | 148 | *bev = bufferevent_openssl_socket_new( 149 | base, fd, ssl, BUFFEREVENT_SSL_CONNECTING, 150 | BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); 151 | #ifdef BUFFEREVENT_SSL_BATCH_WRITE 152 | bufferevent_ssl_set_flags(*bev, BUFFEREVENT_SSL_DIRTY_SHUTDOWN | 153 | BUFFEREVENT_SSL_BATCH_WRITE); 154 | #else 155 | bufferevent_openssl_set_allow_dirty_shutdown(*bev, 1); 156 | #endif 157 | return 0; 158 | } 159 | 160 | // ===================================================== 161 | static int new_session_cb(SSL *ssl, SSL_SESSION *session) 162 | { 163 | if (SESSION_CACHE_LIST == NULL) 164 | return 0; 165 | const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); 166 | if (sni != NULL) { 167 | pgs_list_node_t *cur = NULL, *next = NULL; 168 | pgs_ssl_sessions_cache_t *cache = NULL; 169 | pgs_list_foreach(SESSION_CACHE_LIST, cur, next) 170 | { 171 | cache = (pgs_ssl_sessions_cache_t *)(cur->val); 172 | if (strcmp(cache->sni, sni) == 0) { 173 | break; 174 | } 175 | } 176 | if (cache != NULL) { 177 | pthread_mutex_lock(&cache->lock); 178 | pgs_list_add(cache->ssl_sessions, 179 | pgs_list_node_new(session)); 180 | pthread_mutex_unlock(&cache->lock); 181 | } 182 | } 183 | 184 | return 0; 185 | } 186 | 187 | static void remove_session_cb(SSL_CTX *_, SSL_SESSION *session) 188 | { 189 | if (SESSION_CACHE_LIST == NULL) 190 | return; 191 | 192 | pgs_list_node_t *cur = NULL, *next = NULL; 193 | pgs_ssl_sessions_cache_t *cache = NULL; 194 | bool found = false; 195 | pgs_list_foreach(SESSION_CACHE_LIST, cur, next) 196 | { 197 | cache = (pgs_ssl_sessions_cache_t *)(cur->val); 198 | 199 | pgs_list_node_t *scur = NULL, *snext = NULL; 200 | pgs_list_foreach(cache->ssl_sessions, scur, snext) 201 | { 202 | if (scur->val == session) { 203 | found = true; 204 | break; 205 | } 206 | } 207 | if (found) { 208 | break; 209 | } 210 | } 211 | 212 | if (cache != NULL && found) { 213 | pthread_mutex_lock(&cache->lock); 214 | pgs_list_del_val(cache->ssl_sessions, session); 215 | pthread_mutex_unlock(&cache->lock); 216 | } 217 | } 218 | 219 | static pgs_ssl_sessions_cache_t *session_cache_new(const char *sni) 220 | { 221 | pgs_ssl_sessions_cache_t *ptr = 222 | malloc(sizeof(pgs_ssl_sessions_cache_t)); 223 | ptr->ssl_sessions = pgs_list_new(); 224 | if (pthread_mutex_init(&ptr->lock, NULL) != 0) { 225 | goto error; 226 | } 227 | 228 | ptr->sni = sni; 229 | 230 | return ptr; 231 | error: 232 | session_cache_free(ptr); 233 | return NULL; 234 | } 235 | 236 | static void session_cache_free(pgs_ssl_sessions_cache_t *ptr) 237 | { 238 | if (ptr != NULL) { 239 | pthread_mutex_destroy(&ptr->lock); 240 | pgs_list_free(ptr->ssl_sessions); 241 | free(ptr); 242 | ptr = NULL; 243 | } 244 | } 245 | 246 | static SSL_SESSION *get_session_from_cache(const char *sni) 247 | { 248 | SSL_SESSION *session = NULL; 249 | if (SESSION_CACHE_LIST == NULL) 250 | return NULL; 251 | if (sni == NULL) 252 | return NULL; 253 | pgs_list_node_t *cur = NULL, *next = NULL; 254 | pgs_ssl_sessions_cache_t *cache = NULL; 255 | pgs_list_foreach(SESSION_CACHE_LIST, cur, next) 256 | { 257 | cache = (pgs_ssl_sessions_cache_t *)(cur->val); 258 | if (strcmp(cache->sni, sni) == 0) { 259 | break; 260 | } 261 | } 262 | if (cache != NULL) { 263 | pthread_mutex_lock(&cache->lock); 264 | if (cache->ssl_sessions->len > 0) { 265 | session = cache->ssl_sessions->head->val; 266 | } 267 | pthread_mutex_unlock(&cache->lock); 268 | } 269 | return session; 270 | } 271 | -------------------------------------------------------------------------------- /3rd-party/fnv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * fnv - Fowler/Noll/Vo- hash code 3 | * 4 | * @(#) $Revision: 5.4 $ 5 | * @(#) $Id: fnv.h,v 5.4 2009/07/30 22:49:13 chongo Exp $ 6 | * @(#) $Source: /usr/local/src/cmd/fnv/RCS/fnv.h,v $ 7 | * 8 | *** 9 | * 10 | * Fowler/Noll/Vo- hash 11 | * 12 | * The basis of this hash algorithm was taken from an idea sent 13 | * as reviewer comments to the IEEE POSIX P1003.2 committee by: 14 | * 15 | * Phong Vo (http://www.research.att.com/info/kpv/) 16 | * Glenn Fowler (http://www.research.att.com/~gsf/) 17 | * 18 | * In a subsequent ballot round: 19 | * 20 | * Landon Curt Noll (http://www.isthe.com/chongo/) 21 | * 22 | * improved on their algorithm. Some people tried this hash 23 | * and found that it worked rather well. In an EMail message 24 | * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash. 25 | * 26 | * FNV hashes are designed to be fast while maintaining a low 27 | * collision rate. The FNV speed allows one to quickly hash lots 28 | * of data while maintaining a reasonable collision rate. See: 29 | * 30 | * http://www.isthe.com/chongo/tech/comp/fnv/index.html 31 | * 32 | * for more details as well as other forms of the FNV hash. 33 | * 34 | *** 35 | * 36 | * NOTE: The FNV-0 historic hash is not recommended. One should use 37 | * the FNV-1 hash instead. 38 | * 39 | * To use the 32 bit FNV-0 historic hash, pass FNV0_32_INIT as the 40 | * Fnv32_t hashval argument to fnv_32_buf() or fnv_32_str(). 41 | * 42 | * To use the 64 bit FNV-0 historic hash, pass FNV0_64_INIT as the 43 | * Fnv64_t hashval argument to fnv_64_buf() or fnv_64_str(). 44 | * 45 | * To use the recommended 32 bit FNV-1 hash, pass FNV1_32_INIT as the 46 | * Fnv32_t hashval argument to fnv_32_buf() or fnv_32_str(). 47 | * 48 | * To use the recommended 64 bit FNV-1 hash, pass FNV1_64_INIT as the 49 | * Fnv64_t hashval argument to fnv_64_buf() or fnv_64_str(). 50 | * 51 | * To use the recommended 32 bit FNV-1a hash, pass FNV1_32A_INIT as the 52 | * Fnv32_t hashval argument to fnv_32a_buf() or fnv_32a_str(). 53 | * 54 | * To use the recommended 64 bit FNV-1a hash, pass FNV1A_64_INIT as the 55 | * Fnv64_t hashval argument to fnv_64a_buf() or fnv_64a_str(). 56 | * 57 | *** 58 | * 59 | * Please do not copyright this code. This code is in the public domain. 60 | * 61 | * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 62 | * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO 63 | * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR 64 | * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF 65 | * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 66 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 67 | * PERFORMANCE OF THIS SOFTWARE. 68 | * 69 | * By: 70 | * chongo /\oo/\ 71 | * http://www.isthe.com/chongo/ 72 | * 73 | * Share and Enjoy! :-) 74 | */ 75 | 76 | #if !defined(__FNV_H__) 77 | #define __FNV_H__ 78 | 79 | #include 80 | 81 | #define FNV_VERSION "5.0.2" /* @(#) FNV Version */ 82 | 83 | #include 84 | /* 85 | * 32 bit FNV-0 hash type 86 | */ 87 | typedef uint32_t Fnv32_t; 88 | 89 | 90 | /* 91 | * 32 bit FNV-0 zero initial basis 92 | * 93 | * This historic hash is not recommended. One should use 94 | * the FNV-1 hash and initial basis instead. 95 | */ 96 | #define FNV0_32_INIT ((Fnv32_t)0) 97 | 98 | 99 | /* 100 | * 32 bit FNV-1 and FNV-1a non-zero initial basis 101 | * 102 | * The FNV-1 initial basis is the FNV-0 hash of the following 32 octets: 103 | * 104 | * chongo /\../\ 105 | * 106 | * NOTE: The \'s above are not back-slashing escape characters. 107 | * They are literal ASCII backslash 0x5c characters. 108 | * 109 | * NOTE: The FNV-1a initial basis is the same value as FNV-1 by definition. 110 | */ 111 | #define FNV1_32_INIT ((Fnv32_t)0x811c9dc5) 112 | #define FNV1_32A_INIT FNV1_32_INIT 113 | 114 | 115 | /* 116 | * determine how 64 bit unsigned values are represented 117 | */ 118 | #include "longlong.h" 119 | 120 | 121 | /* 122 | * 64 bit FNV-0 hash 123 | */ 124 | #if defined(HAVE_64BIT_LONG_LONG) 125 | typedef uint64_t Fnv64_t; 126 | #else /* HAVE_64BIT_LONG_LONG */ 127 | typedef struct { 128 | uint32_t w32[2]; /* w32[0] is low order, w32[1] is high order word */ 129 | } Fnv64_t; 130 | #endif /* HAVE_64BIT_LONG_LONG */ 131 | 132 | 133 | /* 134 | * 64 bit FNV-0 zero initial basis 135 | * 136 | * This historic hash is not recommended. One should use 137 | * the FNV-1 hash and initial basis instead. 138 | */ 139 | #if defined(HAVE_64BIT_LONG_LONG) 140 | #define FNV0_64_INIT ((Fnv64_t)0) 141 | #else /* HAVE_64BIT_LONG_LONG */ 142 | extern const Fnv64_t fnv0_64_init; 143 | #define FNV0_64_INIT (fnv0_64_init) 144 | #endif /* HAVE_64BIT_LONG_LONG */ 145 | 146 | 147 | /* 148 | * 64 bit FNV-1 non-zero initial basis 149 | * 150 | * The FNV-1 initial basis is the FNV-0 hash of the following 32 octets: 151 | * 152 | * chongo /\../\ 153 | * 154 | * NOTE: The \'s above are not back-slashing escape characters. 155 | * They are literal ASCII backslash 0x5c characters. 156 | * 157 | * NOTE: The FNV-1a initial basis is the same value as FNV-1 by definition. 158 | */ 159 | #if defined(HAVE_64BIT_LONG_LONG) 160 | #define FNV1_64_INIT ((Fnv64_t)0xcbf29ce484222325ULL) 161 | #define FNV1A_64_INIT FNV1_64_INIT 162 | #else /* HAVE_64BIT_LONG_LONG */ 163 | extern const fnv1_64_init; 164 | extern const Fnv64_t fnv1a_64_init; 165 | #define FNV1_64_INIT (fnv1_64_init) 166 | #define FNV1A_64_INIT (fnv1a_64_init) 167 | #endif /* HAVE_64BIT_LONG_LONG */ 168 | 169 | 170 | /* 171 | * hash types 172 | */ 173 | enum fnv_type { 174 | FNV_NONE = 0, /* invalid FNV hash type */ 175 | FNV0_32 = 1, /* FNV-0 32 bit hash */ 176 | FNV1_32 = 2, /* FNV-1 32 bit hash */ 177 | FNV1a_32 = 3, /* FNV-1a 32 bit hash */ 178 | FNV0_64 = 4, /* FNV-0 64 bit hash */ 179 | FNV1_64 = 5, /* FNV-1 64 bit hash */ 180 | FNV1a_64 = 6, /* FNV-1a 64 bit hash */ 181 | }; 182 | 183 | 184 | /* 185 | * these test vectors are used as part o the FNV test suite 186 | */ 187 | struct test_vector { 188 | void *buf; /* start of test vector buffer */ 189 | int len; /* length of test vector */ 190 | }; 191 | struct fnv0_32_test_vector { 192 | struct test_vector *test; /* test vector buffer to hash */ 193 | Fnv32_t fnv0_32; /* expected FNV-0 32 bit hash value */ 194 | }; 195 | struct fnv1_32_test_vector { 196 | struct test_vector *test; /* test vector buffer to hash */ 197 | Fnv32_t fnv1_32; /* expected FNV-1 32 bit hash value */ 198 | }; 199 | struct fnv1a_32_test_vector { 200 | struct test_vector *test; /* test vector buffer to hash */ 201 | Fnv32_t fnv1a_32; /* expected FNV-1a 32 bit hash value */ 202 | }; 203 | struct fnv0_64_test_vector { 204 | struct test_vector *test; /* test vector buffer to hash */ 205 | Fnv64_t fnv0_64; /* expected FNV-0 64 bit hash value */ 206 | }; 207 | struct fnv1_64_test_vector { 208 | struct test_vector *test; /* test vector buffer to hash */ 209 | Fnv64_t fnv1_64; /* expected FNV-1 64 bit hash value */ 210 | }; 211 | struct fnv1a_64_test_vector { 212 | struct test_vector *test; /* test vector buffer to hash */ 213 | Fnv64_t fnv1a_64; /* expected FNV-1a 64 bit hash value */ 214 | }; 215 | 216 | 217 | /* 218 | * external functions 219 | */ 220 | /* hash_32.c */ 221 | extern Fnv32_t fnv_32_buf(void *buf, size_t len, Fnv32_t hashval); 222 | extern Fnv32_t fnv_32_str(char *buf, Fnv32_t hashval); 223 | 224 | /* hash_32a.c */ 225 | extern Fnv32_t fnv_32a_buf(void *buf, size_t len, Fnv32_t hashval); 226 | extern Fnv32_t fnv_32a_str(char *buf, Fnv32_t hashval); 227 | 228 | /* hash_64.c */ 229 | extern Fnv64_t fnv_64_buf(void *buf, size_t len, Fnv64_t hashval); 230 | extern Fnv64_t fnv_64_str(char *buf, Fnv64_t hashval); 231 | 232 | /* hash_64a.c */ 233 | extern Fnv64_t fnv_64a_buf(void *buf, size_t len, Fnv64_t hashval); 234 | extern Fnv64_t fnv_64a_str(char *buf, Fnv64_t hashval); 235 | 236 | /* test_fnv.c */ 237 | extern struct test_vector fnv_test_str[]; 238 | extern struct fnv0_32_test_vector fnv0_32_vector[]; 239 | extern struct fnv1_32_test_vector fnv1_32_vector[]; 240 | extern struct fnv1a_32_test_vector fnv1a_32_vector[]; 241 | extern struct fnv0_64_test_vector fnv0_64_vector[]; 242 | extern struct fnv1_64_test_vector fnv1_64_vector[]; 243 | extern struct fnv1a_64_test_vector fnv1a_64_vector[]; 244 | extern void unknown_hash_type(char *prog, enum fnv_type type, int code); 245 | extern void print_fnv32(Fnv32_t hval, Fnv32_t mask, int verbose, char *arg); 246 | extern void print_fnv64(Fnv64_t hval, Fnv64_t mask, int verbose, char *arg); 247 | 248 | 249 | #endif /* __FNV_H__ */ 250 | -------------------------------------------------------------------------------- /include/pegasocks/crypto.h: -------------------------------------------------------------------------------- 1 | #ifndef _PGS_CRYPTO_H 2 | #define _PGS_CRYPTO_H 3 | 4 | #include "defs.h" 5 | #include "sha3.h" 6 | #include "fnv.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define SHA224_LEN 28 15 | #define MD5_LEN 16 16 | 17 | #define AES_128_CFB_KEY_LEN 16 18 | #define AES_128_CFB_IV_LEN 16 19 | #define AEAD_AES_128_GCM_KEY_LEN 16 20 | #define AEAD_AES_128_GCM_IV_LEN 12 21 | #define AEAD_AES_128_GCM_TAG_LEN 16 22 | #define AEAD_AES_256_GCM_KEY_LEN 32 23 | #define AEAD_AES_256_GCM_IV_LEN 12 24 | #define AEAD_AES_256_GCM_TAG_LEN 16 25 | #define AEAD_CHACHA20_POLY1305_KEY_LEN 32 26 | #define AEAD_CHACHA20_POLY1305_IV_LEN 12 27 | #define AEAD_CHACHA20_POLY1305_TAG_LEN 16 28 | 29 | typedef struct pgs_base_cryptor_s pgs_aes_cryptor_t; 30 | typedef enum { PGS_ENCRYPT, PGS_DECRYPT } pgs_cryptor_direction_t; 31 | typedef enum { 32 | AES_128_CFB = 0x00, /* vmess */ 33 | AEAD_AES_128_GCM = 0x03, /* vmess */ 34 | AEAD_CHACHA20_POLY1305 = 0x04, /* shadowsocks | vmess */ 35 | AEAD_AES_256_GCM = 0x05, /* shadowsocks */ 36 | } pgs_cryptor_type_t; 37 | 38 | typedef struct pgs_cryptor_s { 39 | pgs_cryptor_type_t cipher; 40 | pgs_cryptor_direction_t dir; 41 | const uint8_t *key; 42 | const uint8_t *iv; 43 | size_t key_len; 44 | size_t iv_len; 45 | size_t tag_len; 46 | void *ctx; 47 | } pgs_cryptor_t; 48 | 49 | pgs_cryptor_t *pgs_cryptor_new(pgs_cryptor_type_t cipher, 50 | pgs_cryptor_direction_t dir, const uint8_t *key, 51 | const uint8_t *iv); 52 | void pgs_cryptor_free(pgs_cryptor_t *cryptor); 53 | 54 | bool pgs_cryptor_encrypt(pgs_cryptor_t *ptr, const uint8_t *plaintext, 55 | size_t plaintext_len, uint8_t *tag, 56 | uint8_t *ciphertext, size_t *ciphertext_len); 57 | bool pgs_cryptor_decrypt(pgs_cryptor_t *ptr, const uint8_t *ciphertext, 58 | size_t ciphertext_len, const uint8_t *tag, 59 | uint8_t *plaintext, size_t *plaintext_len); 60 | 61 | // only needed by aead cipher 62 | void pgs_cryptor_reset_iv(pgs_cryptor_t *ptr, const uint8_t *iv); 63 | 64 | static inline bool is_aead_cipher(pgs_cryptor_type_t cipher) 65 | { 66 | switch (cipher) { 67 | case AES_128_CFB: 68 | return false; 69 | case AEAD_AES_128_GCM: 70 | case AEAD_AES_256_GCM: 71 | case AEAD_CHACHA20_POLY1305: 72 | default: 73 | return true; 74 | } 75 | } 76 | 77 | static inline bool is_aead_cryptor(pgs_cryptor_t *ptr) 78 | { 79 | return is_aead_cipher(ptr->cipher); 80 | } 81 | 82 | static inline void pgs_cryptor_type_info(pgs_cryptor_type_t cipher, 83 | size_t *key_len, size_t *iv_len, 84 | size_t *tag_len) 85 | { 86 | switch (cipher) { 87 | case AES_128_CFB: 88 | *key_len = AES_128_CFB_KEY_LEN; 89 | *iv_len = AES_128_CFB_IV_LEN; 90 | *tag_len = 0; 91 | break; 92 | case AEAD_AES_128_GCM: 93 | *key_len = AEAD_AES_128_GCM_KEY_LEN; 94 | *iv_len = AEAD_AES_128_GCM_IV_LEN; 95 | *tag_len = AEAD_AES_128_GCM_TAG_LEN; 96 | break; 97 | case AEAD_AES_256_GCM: 98 | *key_len = AEAD_AES_256_GCM_KEY_LEN; 99 | *iv_len = AEAD_AES_256_GCM_IV_LEN; 100 | *tag_len = AEAD_AES_256_GCM_TAG_LEN; 101 | break; 102 | case AEAD_CHACHA20_POLY1305: 103 | *key_len = AEAD_CHACHA20_POLY1305_KEY_LEN; 104 | *iv_len = AEAD_CHACHA20_POLY1305_IV_LEN; 105 | *tag_len = AEAD_CHACHA20_POLY1305_TAG_LEN; 106 | break; 107 | default: 108 | break; 109 | } 110 | } 111 | 112 | /* helpers */ 113 | 114 | int rand_bytes(unsigned char *buf, int num); 115 | void sha224(const uint8_t *input, uint64_t input_len, uint8_t *res, 116 | uint64_t *res_len); 117 | void md5(const uint8_t *input, uint64_t input_len, 118 | uint8_t *res /* output len should be 16 */); 119 | void sha1(const uint8_t *input, uint64_t input_len, 120 | uint8_t *res /* output len should be 20 */); 121 | void hmac_md5(const uint8_t *key, uint64_t key_len, const uint8_t *data, 122 | uint64_t data_len, uint8_t *out, uint64_t *out_len); 123 | int aes_128_cfb_encrypt(const uint8_t *plaintext, int plaintext_len, 124 | const uint8_t *key, const uint8_t *iv, 125 | uint8_t *ciphertext); 126 | int aes_128_cfb_decrypt(const uint8_t *ciphertext, int ciphertext_len, 127 | const uint8_t *key, const uint8_t *iv, 128 | uint8_t *plaintext); 129 | 130 | /* shadowsocks AEAD key and salt generation 131 | * password --(evp_bytes_to_key)--> ikm 132 | * random salt + ikm --(hkdf_sha1_extract) --> prk(pseudorandom key) 133 | * prk + "ss-subkey"(as info) --(hkdf_sha1_expand)--> AEAD key 134 | * Extract-and-Expand mode HKDF 135 | * */ 136 | bool hkdf_sha1(const uint8_t *salt, size_t salt_len, const uint8_t *ikm, 137 | size_t ikm_len, const uint8_t *info, size_t info_len, 138 | uint8_t *okm, size_t okm_len); 139 | 140 | // =========================== static helpers 141 | 142 | /* shadowsocks password to key transform */ 143 | static void evp_bytes_to_key(const uint8_t *input, size_t input_len, 144 | uint8_t *key, size_t key_len) 145 | { 146 | uint8_t round_res[16] = { 0 }; 147 | size_t cur_pos = 0; 148 | 149 | uint8_t *buf = (uint8_t *)malloc(input_len + 16); 150 | memcpy(buf, input, input_len); 151 | 152 | while (cur_pos < key_len) { 153 | if (cur_pos == 0) { 154 | md5(buf, input_len, round_res); 155 | } else { 156 | memcpy(buf, round_res, 16); 157 | memcpy(buf + 16, input, input_len); 158 | md5(buf, input_len + 16, round_res); 159 | } 160 | for (int p = cur_pos; p < key_len && p < cur_pos + 16; p++) { 161 | key[p] = round_res[p - cur_pos]; 162 | } 163 | cur_pos += 16; 164 | } 165 | free(buf); 166 | } 167 | 168 | static void shake128(const uint8_t *input, uint64_t input_len, uint8_t *out, 169 | uint64_t out_len) 170 | { 171 | sha3_ctx_t sha3; 172 | shake128_init(&sha3); 173 | shake_update(&sha3, input, input_len); 174 | shake_xof(&sha3); 175 | shake_out(&sha3, out, out_len); 176 | return; 177 | } 178 | 179 | static int fnv1a(void *input, uint64_t input_len) 180 | { 181 | return fnv_32a_buf(input, input_len, FNV1_32A_INIT); 182 | } 183 | 184 | static uint8_t *to_hexstring(const uint8_t *buf, uint64_t size) 185 | { 186 | uint8_t *hexbuf = (uint8_t *)malloc(sizeof(uint8_t) * (2 * size + 1)); 187 | for (int i = 0; i < size; i++) { 188 | sprintf((char *)hexbuf + i * 2, "%02x", (int)buf[i]); 189 | } 190 | hexbuf[2 * size] = '\0'; 191 | return hexbuf; 192 | } 193 | 194 | static void debug_hexstring(const char *name, const uint8_t *buf, uint64_t size) 195 | { 196 | uint8_t *hexbuf = (uint8_t *)malloc(sizeof(uint8_t) * (2 * size + 1)); 197 | for (int i = 0; i < size; i++) { 198 | sprintf((char *)hexbuf + i * 2, "%02x", (int)buf[i]); 199 | } 200 | hexbuf[2 * size] = '\0'; 201 | printf("%s:\n%s\n", name, hexbuf); 202 | free(hexbuf); 203 | } 204 | 205 | static void hextobin(const char *str, uint8_t *bytes, size_t blen) 206 | { 207 | uint8_t pos; 208 | uint8_t idx0; 209 | uint8_t idx1; 210 | 211 | // mapping of ASCII characters to hex values 212 | const uint8_t hashmap[] = { 213 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 214 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 215 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 216 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 217 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // !"#$%&' 218 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ()*+,-./ 219 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567 220 | 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>? 221 | 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // @ABCDEFG 222 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // HIJKLMNO 223 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // PQRSTUVW 224 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // XYZ[\]^_ 225 | 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // `abcdefg 226 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // hijklmno 227 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pqrstuvw 228 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // xyz{|}~. 229 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 230 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 231 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 232 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 233 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 234 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 235 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 236 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 237 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 238 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 239 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 240 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 241 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 242 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 243 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ 244 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // ........ 245 | }; 246 | 247 | memzero(bytes, blen); 248 | for (pos = 0; (pos < (blen * 2)); pos += 2) { 249 | idx0 = (uint8_t)str[pos + 0]; 250 | idx1 = (uint8_t)str[pos + 1]; 251 | bytes[pos / 2] = (uint8_t)(hashmap[idx0] << 4) | hashmap[idx1]; 252 | }; 253 | } 254 | 255 | static void pgs_increase_nonce(uint8_t *nonce, size_t bytes) 256 | { 257 | uint16_t c = 1; 258 | // increment 1 in little endian 259 | for (size_t i = 0; i < bytes; ++i) { 260 | c += nonce[i]; 261 | nonce[i] = c & 0xff; 262 | c >>= 8; 263 | } 264 | } 265 | #endif 266 | -------------------------------------------------------------------------------- /src/pegas.c: -------------------------------------------------------------------------------- 1 | #include "pegas.h" 2 | #include "config.h" 3 | #include "acl.h" 4 | #include "mpsc.h" 5 | #include "defs.h" 6 | #include "ssl.h" 7 | 8 | #ifdef WITH_APPLET 9 | #include "applet.h" 10 | #endif 11 | 12 | #include "server/manager.h" 13 | #include "server/helper.h" 14 | #include "server/local.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #ifndef _WIN32 27 | #include 28 | #include 29 | #include 30 | #include 31 | #endif 32 | 33 | static pgs_config_t *CONFIG = NULL; 34 | static pgs_acl_t *PGS_ACL = NULL; 35 | static pgs_mpsc_t *MPSC = NULL; 36 | static pgs_logger_t *LOGGER = NULL; /* log consumer */ 37 | static pgs_server_manager_t *SM = NULL; 38 | static pgs_ssl_ctx_t *SSL_CTX = NULL; 39 | 40 | static pthread_t *THREADS = NULL; 41 | static pgs_local_server_t **LOCAL_SERVERS = NULL; /* array of const points */ 42 | 43 | static pthread_t HELPER_THREAD = 0; 44 | 45 | static pgs_helper_thread_t *HELPER_THREAD_CTX = NULL; /* const pointer */ 46 | 47 | static int lfd = 0; 48 | static int cfd = 0; 49 | static int snum = 0; 50 | 51 | static bool RUNNING = false; 52 | static _Atomic int SHUTINGDOWN = ATOMIC_VAR_INIT(0); 53 | 54 | static bool pgs_init(const char *config, const char *acl, int threads); 55 | static void pgs_clean(); 56 | 57 | static int init_local_server_fd(const pgs_config_t *config, int *fd, 58 | int sock_type); 59 | static int init_control_fd(const pgs_config_t *config, int *fd); 60 | 61 | static bool pgs_start_local_servers(); 62 | static bool pgs_start_helper(); 63 | 64 | bool pgs_start(const char *config, const char *acl, int threads, 65 | void (*shutdown)()) 66 | { 67 | if (RUNNING) 68 | return false; 69 | 70 | if (!pgs_init(config, acl, threads)) 71 | return false; 72 | 73 | if (!pgs_start_local_servers()) 74 | return false; 75 | 76 | if (!pgs_start_helper()) 77 | return false; 78 | 79 | assert(HELPER_THREAD != 0); 80 | 81 | RUNNING = true; 82 | 83 | #ifdef WITH_APPLET 84 | pgs_tray_context_t tray_ctx = { LOGGER, SM, NULL, shutdown }; 85 | pgs_tray_start(&tray_ctx); 86 | #endif 87 | 88 | // will block here 89 | 90 | pthread_join(HELPER_THREAD, NULL); 91 | 92 | // stoped by other threads 93 | 94 | RUNNING = false; 95 | 96 | // cleanup 97 | pgs_clean(); 98 | 99 | return true; 100 | } 101 | 102 | void pgs_stop() 103 | { 104 | if (atomic_load(&SHUTINGDOWN)) 105 | return; 106 | atomic_store(&SHUTINGDOWN, 1); 107 | 108 | if (LOCAL_SERVERS != NULL) { 109 | for (int i = 0; i < snum; i++) { 110 | if (LOCAL_SERVERS[i] != NULL) { 111 | // clean the thread resources 112 | evuser_trigger(LOCAL_SERVERS[i]->ev_term); 113 | } 114 | } 115 | } 116 | 117 | // should join all helper threads here 118 | for (int i = 0; i < snum; i++) { 119 | pthread_join(THREADS[i], NULL); 120 | } 121 | 122 | if (HELPER_THREAD_CTX != NULL) { 123 | evuser_trigger(HELPER_THREAD_CTX->ev_term); 124 | } 125 | 126 | atomic_store(&SHUTINGDOWN, 0); 127 | } 128 | 129 | void pgs_get_version(char *version) 130 | { 131 | sprintf(version, "%s", PGS_VERSION); 132 | } 133 | 134 | void pgs_get_servers(char *out, int max_len, int *olen) 135 | { 136 | pgs_sm_get_servers(SM, out, max_len, olen); 137 | } 138 | 139 | bool pgs_set_server(int idx) 140 | { 141 | return pgs_sm_set_server(SM, idx); 142 | } 143 | 144 | // ======================== static functions 145 | static int init_local_server_fd(const pgs_config_t *config, int *fd, 146 | int sock_type) 147 | { 148 | int err = 0; 149 | struct sockaddr_in sin = { 0 }; 150 | int port = config->local_port; 151 | 152 | memset(&sin, 0, sizeof(sin)); 153 | 154 | sin.sin_family = AF_INET; 155 | err = inet_pton(AF_INET, config->local_address, &sin.sin_addr); 156 | if (err <= 0) { 157 | if (err == 0) 158 | pgs_config_error(config, "Not in presentation format"); 159 | else 160 | perror("inet_pton"); 161 | exit(EXIT_FAILURE); 162 | } 163 | sin.sin_port = htons(port); 164 | 165 | *fd = socket(AF_INET, sock_type, 0); 166 | err = evutil_make_listen_socket_reuseable(*fd); 167 | 168 | if (err < 0) { 169 | perror("setsockopt"); 170 | return err; 171 | } 172 | 173 | err = evutil_make_socket_nonblocking(*fd); 174 | if (err) { 175 | perror("pegas.c evutil_make_socket_nonblocking"); 176 | return err; 177 | } 178 | 179 | err = bind(*fd, (struct sockaddr *)&sin, sizeof(sin)); 180 | 181 | if (err < 0) { 182 | perror("bind"); 183 | return err; 184 | } 185 | return err; 186 | } 187 | 188 | static int init_control_fd(const pgs_config_t *config, int *fd) 189 | { 190 | int err = 0; 191 | if (config->control_port) { 192 | // tcp port 193 | struct sockaddr_in sin; 194 | int port = config->control_port; 195 | 196 | memset(&sin, 0, sizeof(sin)); 197 | 198 | sin.sin_family = AF_INET; 199 | err = inet_pton(AF_INET, config->local_address, &sin.sin_addr); 200 | if (err <= 0) { 201 | if (err == 0) 202 | pgs_config_error(config, 203 | "Not in presentation format"); 204 | else 205 | perror("inet_pton"); 206 | exit(EXIT_FAILURE); 207 | } 208 | sin.sin_port = htons(port); 209 | 210 | *fd = socket(AF_INET, SOCK_STREAM, 0); 211 | 212 | err = evutil_make_socket_nonblocking(*fd); 213 | if (err) { 214 | perror("evutil_make_socket_nonblocking"); 215 | return err; 216 | } 217 | 218 | err = bind(*fd, (struct sockaddr *)&sin, sizeof(sin)); 219 | if (err < 0) { 220 | perror("bind"); 221 | return err; 222 | } 223 | } else if (config->control_file) { 224 | // unix socket 225 | #ifdef _WIN32 226 | perror("unix socket is not supported on windows"); 227 | return err; 228 | #else 229 | struct sockaddr_un server; 230 | *fd = socket(AF_UNIX, SOCK_STREAM, 0); 231 | server.sun_family = AF_UNIX; 232 | strcpy(server.sun_path, config->control_file); 233 | err = evutil_make_socket_nonblocking(*fd); 234 | if (err) { 235 | perror("evutil_make_socket_nonblocking"); 236 | return err; 237 | } 238 | unlink(config->control_file); 239 | err = bind(*fd, (struct sockaddr *)&server, 240 | sizeof(struct sockaddr_un)); 241 | if (err < 0) { 242 | perror("bind"); 243 | return err; 244 | } 245 | #endif 246 | } 247 | 248 | return err; 249 | } 250 | 251 | static bool pgs_start_local_servers() 252 | { 253 | THREADS = malloc(snum * sizeof(pthread_t)); 254 | LOCAL_SERVERS = malloc(snum * sizeof(pgs_local_server_t *)); 255 | for (int i = 0; i < snum; i++) { 256 | LOCAL_SERVERS[i] = NULL; 257 | } 258 | 259 | pthread_attr_t attr; 260 | pthread_attr_init(&attr); 261 | // pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 262 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 263 | 264 | // Local server threads 265 | for (int i = 0; i < snum; i++) { 266 | // ctx is freed in worker threads 267 | pgs_local_server_ctx_t *ctx = 268 | malloc(sizeof(pgs_local_server_ctx_t)); 269 | ctx->fd = lfd; 270 | ctx->mpsc = MPSC; 271 | ctx->config = CONFIG; 272 | ctx->sm = SM; 273 | ctx->acl = PGS_ACL; 274 | ctx->ssl_ctx = SSL_CTX; 275 | ctx->local_server_ref = (void **)&LOCAL_SERVERS[i]; 276 | 277 | pthread_create(&THREADS[i], &attr, start_local_server, ctx); 278 | } 279 | pthread_attr_destroy(&attr); 280 | return true; 281 | } 282 | 283 | /* 284 | * Logger / metrics / control(rpc) / signal 285 | */ 286 | static bool pgs_start_helper() 287 | { 288 | pthread_attr_t attr; 289 | pthread_attr_init(&attr); 290 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 291 | 292 | pgs_helper_thread_ctx_t *ctx = malloc(sizeof(pgs_helper_thread_t)); 293 | ctx->cfd = cfd; 294 | ctx->config = CONFIG; 295 | ctx->logger = LOGGER; 296 | ctx->sm = SM; 297 | ctx->ssl_ctx = SSL_CTX; 298 | ctx->helper_ref = (void **)&HELPER_THREAD_CTX; 299 | 300 | pthread_create(&HELPER_THREAD, &attr, pgs_helper_thread_start, ctx); 301 | 302 | pthread_attr_destroy(&attr); 303 | 304 | return true; 305 | } 306 | 307 | static bool pgs_init(const char *config, const char *acl, int threads) 308 | { 309 | snum = threads; 310 | CONFIG = pgs_config_load(config); 311 | if (CONFIG == NULL) { 312 | perror("failed to load config"); 313 | return false; 314 | } 315 | 316 | #ifdef WITH_ACL 317 | if (acl != NULL) { 318 | PGS_ACL = pgs_acl_new(acl); 319 | if (PGS_ACL == NULL) { 320 | perror("failed to load acl file"); 321 | return false; 322 | } 323 | } else { 324 | if (CONFIG->acl_file != NULL) { 325 | PGS_ACL = pgs_acl_new(CONFIG->acl_file); 326 | if (PGS_ACL == NULL) { 327 | perror("failed to load acl file"); 328 | return false; 329 | } 330 | } 331 | } 332 | #endif 333 | 334 | if (init_local_server_fd(CONFIG, &lfd, SOCK_STREAM) < 0) { 335 | return false; 336 | } 337 | if (init_control_fd(CONFIG, &cfd) < 0) { 338 | return false; 339 | } 340 | MPSC = pgs_mpsc_new(DEFAULT_LOG_MPSC_SIZE); 341 | 342 | LOGGER = pgs_logger_new(MPSC, CONFIG->log_level, CONFIG->log_isatty); 343 | 344 | SM = pgs_server_manager_new(CONFIG->servers, CONFIG->servers_count); 345 | 346 | SSL_CTX = pgs_ssl_ctx_new(CONFIG); 347 | 348 | return true; 349 | } 350 | 351 | static void pgs_clean() 352 | { 353 | if (LOCAL_SERVERS != NULL) { 354 | for (int i = 0; i < snum; i++) { 355 | if (LOCAL_SERVERS[i] != NULL) { 356 | LOCAL_SERVERS[i] = NULL; 357 | } 358 | } 359 | free(LOCAL_SERVERS); 360 | LOCAL_SERVERS = NULL; 361 | } 362 | 363 | if (THREADS != NULL) { 364 | free(THREADS); 365 | THREADS = NULL; 366 | } 367 | if (HELPER_THREAD_CTX != NULL) { 368 | HELPER_THREAD_CTX = NULL; 369 | } 370 | if (HELPER_THREAD != 0) { 371 | HELPER_THREAD = 0; 372 | } 373 | if (SSL_CTX != NULL) { 374 | pgs_ssl_ctx_free(SSL_CTX); 375 | SSL_CTX = NULL; 376 | } 377 | if (SM != NULL) { 378 | pgs_server_manager_free(SM); 379 | SM = NULL; 380 | } 381 | if (LOGGER != NULL) { 382 | pgs_logger_free(LOGGER); 383 | LOGGER = NULL; 384 | } 385 | if (MPSC != NULL) { 386 | pgs_mpsc_free(MPSC); 387 | MPSC = NULL; 388 | } 389 | 390 | if (CONFIG != NULL) { 391 | pgs_config_free(CONFIG); 392 | CONFIG = NULL; 393 | } 394 | #ifdef WITH_ACL 395 | if (PGS_ACL != NULL) { 396 | pgs_acl_free(PGS_ACL); 397 | PGS_ACL = NULL; 398 | } 399 | #endif 400 | 401 | // will be closed by bufferevents 402 | lfd = 0; 403 | cfd = 0; 404 | 405 | snum = 0; 406 | } 407 | -------------------------------------------------------------------------------- /src/codec/shadowsocks.c: -------------------------------------------------------------------------------- 1 | #include "codec/codec.h" 2 | #include "crypto.h" 3 | 4 | #include 5 | 6 | static bool shadowsocks_write_local_aes(pgs_session_t *session, 7 | const uint8_t *msg, size_t len, 8 | size_t *olen, size_t *clen); 9 | static bool shadowsocks_write_local_aead(pgs_session_t *session, 10 | const uint8_t *msg, size_t len, 11 | size_t *olen, size_t *clen); 12 | 13 | static bool shadowsocks_write_remote_aes(pgs_session_t *session, 14 | const uint8_t *msg, size_t len, 15 | size_t *olen); 16 | static bool shadowsocks_write_remote_aead(pgs_session_t *session, 17 | const uint8_t *msg, size_t len, 18 | size_t *olen); 19 | 20 | static void pgs_ss_increase_cryptor_iv(pgs_outbound_ctx_ss_t *ctx, 21 | pgs_cryptor_direction_t dir); 22 | 23 | bool shadowsocks_write_remote(pgs_session_t *session, const uint8_t *msg, 24 | size_t len, size_t *olen) 25 | { 26 | pgs_outbound_ctx_ss_t *ssctx = session->outbound->ctx; 27 | 28 | if (is_aead_cipher(ssctx->cipher)) { 29 | return shadowsocks_write_remote_aead(session, msg, len, olen); 30 | } else { 31 | return shadowsocks_write_remote_aes(session, msg, len, olen); 32 | } 33 | } 34 | 35 | bool shadowsocks_write_local(pgs_session_t *session, const uint8_t *msg, 36 | size_t len, size_t *olen, 37 | size_t *clen /* consumed len */) 38 | { 39 | pgs_outbound_ctx_ss_t *ssctx = session->outbound->ctx; 40 | if (is_aead_cipher(ssctx->cipher)) { 41 | return shadowsocks_write_local_aead(session, msg, len, olen, 42 | clen); 43 | } else { 44 | return shadowsocks_write_local_aes(session, msg, len, olen, 45 | clen); 46 | } 47 | } 48 | 49 | // static 50 | static void pgs_ss_increase_cryptor_iv(pgs_outbound_ctx_ss_t *ctx, 51 | pgs_cryptor_direction_t dir) 52 | { 53 | if (is_aead_cipher(ctx->cipher)) { 54 | pgs_cryptor_t *cryptor = NULL; 55 | uint8_t *iv = NULL; 56 | switch (dir) { 57 | case PGS_DECRYPT: 58 | cryptor = ctx->decryptor; 59 | iv = ctx->dec_iv; 60 | break; 61 | case PGS_ENCRYPT: 62 | cryptor = ctx->encryptor; 63 | iv = ctx->enc_iv; 64 | break; 65 | default: 66 | break; 67 | } 68 | 69 | if (iv != NULL && cryptor != NULL) { 70 | pgs_increase_nonce(iv, ctx->iv_len); 71 | pgs_cryptor_reset_iv(cryptor, iv); 72 | } 73 | } 74 | } 75 | 76 | static bool shadowsocks_write_local_aes(pgs_session_t *session, 77 | const uint8_t *msg, size_t len, 78 | size_t *olen, size_t *clen) 79 | { 80 | struct bufferevent *inbev = session->inbound->bev; 81 | struct evbuffer *inboundw = bufferevent_get_output(inbev); 82 | 83 | pgs_outbound_ctx_ss_t *ssctx = session->outbound->ctx; 84 | 85 | size_t offset = 0, decode_len = 0; 86 | if (ssctx->decryptor == NULL) { 87 | if (len < ssctx->iv_len) { 88 | pgs_session_error(session, "need data for iv"); 89 | return false; 90 | } 91 | memcpy(ssctx->dec_iv, msg, ssctx->iv_len); 92 | memcpy(ssctx->dec_key, ssctx->ikm, ssctx->key_len); 93 | ssctx->decryptor = 94 | pgs_cryptor_new(ssctx->cipher, PGS_DECRYPT, 95 | ssctx->dec_key, ssctx->dec_iv); 96 | offset += ssctx->iv_len; 97 | } 98 | assert(ssctx->decryptor != NULL); 99 | 100 | size_t mlen = len - offset; 101 | 102 | pgs_buffer_ensure(ssctx->rbuf, mlen); 103 | bool ok = pgs_cryptor_decrypt(ssctx->decryptor, msg + offset, mlen, 104 | NULL, ssctx->rbuf->buffer, &decode_len); 105 | if (!ok || decode_len != mlen) { 106 | return false; 107 | } 108 | evbuffer_add(inboundw, ssctx->rbuf->buffer, mlen); 109 | *olen = mlen; 110 | *clen = len; 111 | return true; 112 | } 113 | 114 | static bool shadowsocks_write_local_aead(pgs_session_t *session, 115 | const uint8_t *msg, size_t len, 116 | size_t *olen, size_t *clen) 117 | { 118 | struct bufferevent *inbev = session->inbound->bev; 119 | struct evbuffer *inboundw = bufferevent_get_output(inbev); 120 | 121 | pgs_outbound_ctx_ss_t *ssctx = session->outbound->ctx; 122 | *clen = 0; 123 | size_t decode_len; 124 | 125 | // init decryptor 126 | if (!ssctx->decryptor) { 127 | if (len < ssctx->key_len) { 128 | pgs_session_error(session, 129 | "need atl least %ld bytes for salt", 130 | ssctx->key_len); 131 | return false; 132 | } 133 | hkdf_sha1(msg /*salt*/, ssctx->key_len, ssctx->ikm, 134 | ssctx->key_len, (const uint8_t *)SS_INFO, 9, 135 | ssctx->dec_key, ssctx->key_len); 136 | ssctx->decryptor = 137 | pgs_cryptor_new(ssctx->cipher, PGS_DECRYPT, 138 | ssctx->dec_key, ssctx->dec_iv); 139 | *clen += ssctx->key_len; 140 | len -= ssctx->key_len; 141 | } 142 | assert(ssctx->decryptor); 143 | 144 | int last_state = ssctx->aead_decode_state; 145 | while (true) { 146 | switch (ssctx->aead_decode_state) { 147 | case READY: { 148 | if (ssctx->plen == 0) { 149 | // parse plen 150 | if (len < 2 + ssctx->tag_len) { 151 | ssctx->aead_decode_state = 152 | WAIT_MORE_FOR_LEN; 153 | pgs_session_debug( 154 | session, 155 | "need more data for payload len"); 156 | return true; 157 | } 158 | uint8_t chunk_len[2]; 159 | pgs_cryptor_decrypt(ssctx->decryptor, 160 | msg + *clen, 2, 161 | msg + *clen + 2, chunk_len, 162 | &decode_len); 163 | pgs_ss_increase_cryptor_iv(ssctx, PGS_DECRYPT); 164 | if (decode_len != 2) { 165 | return false; 166 | } 167 | *clen += (2 + ssctx->tag_len); 168 | len -= (2 + ssctx->tag_len); 169 | ssctx->plen = (uint16_t)chunk_len[0] << 8 | 170 | chunk_len[1]; 171 | } else { 172 | // parse payload 173 | if (len < ssctx->plen + ssctx->tag_len) { 174 | ssctx->aead_decode_state = 175 | WAIT_MORE_FOR_PAYLOAD; 176 | pgs_session_debug( 177 | session, 178 | "need more data for payload"); 179 | return true; 180 | } 181 | 182 | pgs_buffer_ensure(ssctx->rbuf, ssctx->plen); 183 | pgs_cryptor_decrypt(ssctx->decryptor, 184 | msg + *clen, ssctx->plen, 185 | msg + *clen + ssctx->plen, 186 | ssctx->rbuf->buffer, 187 | &decode_len); 188 | pgs_ss_increase_cryptor_iv(ssctx, PGS_DECRYPT); 189 | if (decode_len != ssctx->plen) { 190 | return false; 191 | } 192 | evbuffer_add(inboundw, ssctx->rbuf->buffer, 193 | ssctx->plen); 194 | *olen += ssctx->plen; 195 | *clen += (ssctx->plen + ssctx->tag_len); 196 | len -= (ssctx->plen + ssctx->tag_len); 197 | ssctx->plen = 0; 198 | } 199 | break; 200 | } 201 | case WAIT_MORE_FOR_LEN: { 202 | if (len < 2 + ssctx->tag_len) { 203 | pgs_session_debug( 204 | session, 205 | "need more data for payload len"); 206 | return true; 207 | } 208 | ssctx->aead_decode_state = READY; 209 | break; 210 | } 211 | case WAIT_MORE_FOR_PAYLOAD: { 212 | if (len < ssctx->plen + ssctx->tag_len) { 213 | pgs_session_debug(session, 214 | "need more data for payload"); 215 | return true; 216 | } 217 | ssctx->aead_decode_state = READY; 218 | break; 219 | } 220 | } 221 | } 222 | } 223 | 224 | static bool shadowsocks_write_remote_aes(pgs_session_t *session, 225 | const uint8_t *msg, size_t len, 226 | size_t *olen) 227 | { 228 | struct bufferevent *outbev = session->outbound->bev; 229 | struct evbuffer *outboundw = bufferevent_get_output(outbev); 230 | pgs_outbound_ctx_ss_t *ssctx = session->outbound->ctx; 231 | 232 | // stream: [iv][chunk] 233 | // aes chunk: [encrypted payload] 234 | // first chunk [cmd[3:]][data] 235 | size_t ciphertext_len; 236 | 237 | if (!ssctx->iv_sent) { 238 | const uint8_t *iv = ssctx->enc_iv; 239 | size_t iv_len = ssctx->iv_len; 240 | 241 | pgs_buffer_ensure(ssctx->wbuf, iv_len); 242 | memcpy(ssctx->wbuf->buffer, iv, iv_len); 243 | 244 | ssctx->iv_sent = true; 245 | 246 | size_t addr_len = ssctx->cmd_len - 3; 247 | size_t chunk_len = addr_len + len; 248 | 249 | uint8_t *payload = malloc(chunk_len); 250 | memcpy(payload, ssctx->cmd + 3, addr_len); 251 | memcpy(payload + addr_len, msg, len); 252 | 253 | pgs_buffer_ensure(ssctx->wbuf, chunk_len + iv_len); 254 | bool ok = pgs_cryptor_encrypt(ssctx->encryptor, payload, 255 | chunk_len, NULL, 256 | ssctx->wbuf->buffer + iv_len, 257 | &ciphertext_len); 258 | free(payload); 259 | 260 | if (!ok || ciphertext_len != chunk_len) { 261 | pgs_session_error(session, 262 | "shadowsocks encrypt failed"); 263 | return false; 264 | } 265 | 266 | *olen = iv_len + chunk_len; 267 | } else { 268 | pgs_buffer_ensure(ssctx->wbuf, len); 269 | bool ok = pgs_cryptor_encrypt(ssctx->encryptor, msg, len, NULL, 270 | ssctx->wbuf->buffer, 271 | &ciphertext_len); 272 | if (!ok || ciphertext_len != len) { 273 | pgs_session_error(session, 274 | "shadowsocks encrypt failed"); 275 | return false; 276 | } 277 | *olen = len; 278 | } 279 | evbuffer_add(outboundw, ssctx->wbuf->buffer, *olen); 280 | return true; 281 | } 282 | 283 | static bool shadowsocks_write_remote_aead(pgs_session_t *session, 284 | const uint8_t *msg, size_t len, 285 | size_t *olen) 286 | { 287 | struct bufferevent *outbev = session->outbound->bev; 288 | struct evbuffer *outboundw = bufferevent_get_output(outbev); 289 | pgs_outbound_ctx_ss_t *ssctx = session->outbound->ctx; 290 | 291 | // stream: [iv][chunk] 292 | // aead chunk: [encrypted payload length(2)][length tag][encrypted payload][payload tag] 293 | // first chunk: [cmd[3:]][data] 294 | size_t addr_len = ssctx->cmd_len - 3; 295 | size_t payload_len = len; 296 | size_t chunk_len = 2 + ssctx->tag_len + payload_len + ssctx->tag_len; 297 | 298 | size_t offset = 0; 299 | 300 | if (!ssctx->iv_sent) { 301 | const uint8_t *salt = ssctx->enc_salt; 302 | size_t salt_len = ssctx->key_len; 303 | 304 | pgs_buffer_ensure(ssctx->wbuf, salt_len); 305 | memcpy(ssctx->wbuf->buffer, salt, salt_len); 306 | 307 | offset += salt_len; 308 | payload_len = len + addr_len; 309 | } 310 | 311 | if (payload_len > 0x3FFF) { 312 | return false; 313 | } 314 | 315 | uint8_t prefix[2] = { 0 }; 316 | prefix[0] = payload_len >> 8; 317 | prefix[1] = payload_len; 318 | 319 | pgs_buffer_ensure(ssctx->wbuf, offset + 2 + ssctx->tag_len); 320 | size_t ciphertext_len; 321 | pgs_cryptor_encrypt(ssctx->encryptor, prefix, 2, 322 | ssctx->wbuf->buffer + offset + 2 /* tag */, 323 | ssctx->wbuf->buffer + offset, &ciphertext_len); 324 | pgs_ss_increase_cryptor_iv(ssctx, PGS_ENCRYPT); 325 | 326 | if (ciphertext_len != 2) { 327 | return false; 328 | } 329 | offset += (2 + ssctx->tag_len); 330 | 331 | if (!ssctx->iv_sent) { 332 | uint8_t *payload = malloc(payload_len); 333 | memcpy(payload, ssctx->cmd + 3, addr_len); 334 | memcpy(payload + addr_len, msg, len); 335 | 336 | pgs_buffer_ensure(ssctx->wbuf, 337 | offset + payload_len + ssctx->tag_len); 338 | bool ok = pgs_cryptor_encrypt( 339 | ssctx->encryptor, payload, payload_len, 340 | ssctx->wbuf->buffer + offset + payload_len /* tag */, 341 | ssctx->wbuf->buffer + offset, &ciphertext_len); 342 | pgs_ss_increase_cryptor_iv(ssctx, PGS_ENCRYPT); 343 | free(payload); 344 | ssctx->iv_sent = true; 345 | 346 | if (!ok || ciphertext_len != payload_len) { 347 | return false; 348 | } 349 | } else { 350 | pgs_buffer_ensure(ssctx->wbuf, offset + len + ssctx->tag_len); 351 | bool ok = pgs_cryptor_encrypt( 352 | ssctx->encryptor, msg, len, 353 | ssctx->wbuf->buffer + offset + len /* tag */, 354 | ssctx->wbuf->buffer + offset, &ciphertext_len); 355 | pgs_ss_increase_cryptor_iv(ssctx, PGS_ENCRYPT); 356 | 357 | if (!ok || ciphertext_len != payload_len) { 358 | return false; 359 | } 360 | } 361 | 362 | *olen = offset + payload_len + ssctx->tag_len; 363 | evbuffer_add(outboundw, ssctx->wbuf->buffer, *olen); 364 | 365 | return true; 366 | } 367 | --------------------------------------------------------------------------------