upstream#2
Conversation
- New visitor.c/visitor.h: local TCP listener that tunnels connections to named STCP/XTCP/SUDP proxy servers via frps - INI section format: [stcp_visitor:name] with server_name, sk, bind_port - State machine: connect → send NewVisitorConn → validate response → tunnel - config.c: route visitor INI sections to parse_visitor_section() - control.c: init visitors after login, handle TypeNewVisitorConnResp - CMakeLists.txt: add visitor.c to build
- plugin = unix_domain_socket with plugin_unix_path config - connect_unix_server(): non-blocking Unix socket connection via libevent - setup_local_connection(): detects UDS plugin, connects to socket path - Added sys/un.h for sockaddr_un - Config example: test_uds.ini - Works with any proxy type (tcp/http/https) backed by a local UDS service
When SIGHUP is received (e.g. via 'kill -HUP <pid>' or 'reload' command), the client gracefully reloads its configuration without restarting: - Signal handler sets a volatile flag (async-signal-safe) - 500ms periodic timer in the event loop checks the flag - On trigger: stops visitors, clears proxy tunnels, reloads INI, and re-registers proxies with frps over the existing control connection - The frps connection itself is preserved (no re-login needed) Usage: kill -HUP # or if running in foreground: kill -HUP <pid> Changes: - commandline.c/h: SIGHUP handler + reload flag + config file accessor - control.c/h: reload_xfrpc_config() + reload_check_timer_cb() - visitor.c/h: free_all_visitor_instances() for clean visitor shutdown
Adds configurable health checks for proxy services. When a local service fails its health check, the proxy is temporarily unregistered from frps and re-registered once the service recovers. INI config per proxy section: [my_service] type = tcp local_ip = 127.0.0.1 local_port = 8080 remote_port = 80 health_check_type = tcp # tcp or http health_check_interval = 10 # seconds between checks (default 10) health_check_timeout = 3 # per-check timeout (default 3) health_check_max_failed = 1 # consecutive failures before down (default 1) health_check_url = /health # HTTP path for http type (default /) Implementation: - health_check.c/h: libevent-based async TCP/HTTP checks with periodic timers - control.c: integration with proxy registration lifecycle - Skips unhealthy proxies during start_proxy_services() - Re-registers proxy on recovery via health_check_result_cb - Stops health checks on disconnect and config reload - config.c: parsing, defaults, dump, and cleanup for health_check_* fields - client.h: health check config fields in proxy_service struct
Signed-off-by: liudf0716 <liudf0716@gmail.com>
…ndling Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
…ional field support Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
…atibility Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
- Introduced `xtcp_visitor.c` and `xtcp_visitor.h` to implement the XTCP visitor functionality. - Implemented the full NAT hole-punching flow including PreCheck, STUN discovery, and data relaying. - Enhanced `visitor.c` to route XTCP visitors to the new handler. - Added caching for STUN results to optimize subsequent connections. - Implemented UDP socket management for hole-punching and data relay. - Added necessary debug logging for better traceability during the XTCP process. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
…sitor Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
- Add quic_transport.c/h: QUIC transport layer using ngtcp2 + wolfSSL/OpenSSL - Add xtcp_client.c/h: client-side NAT hole-punch and P2P relay - Support wolfSSL and OpenSSL crypto backends for ngtcp2 - Reuse STUN socket for hole-punch to preserve NAT mapping - Add tunnel framing protocol with optional AES-128-CFB encryption - Add reconnect logic with rate limiting for KeepTunnelOpen - Add fallback_to visitor support for XTCP failure - Fix ngtcp2 v1.24 API compatibility (callbacks, settings, transport params) - Fix nathole.c format-truncation warnings - Fix xtcp_visitor.c typedef/function name collisions Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
…rapper - CMakeLists.txt: add USE_WOLFSSL option (default ON), auto-fallback to OpenSSL when wolfSSL is not found. QUIC ngtcp2 crypto backend auto-detects and prefers the matching SSL library. - Remove fastpbkdf2.c/h: was a trivial wrapper around PKCS5_PBKDF2_HMAC(). crypto.c and nathole.c now call PKCS5_PBKDF2_HMAC() directly. - Source code keeps using <openssl/*.h> includes (no #ifdef needed); on OpenWrt, wolfssl package provides compat headers at the standard path. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
- quic_client_transport.c/h: ngtcp2-based QUIC client that connects to frps over UDP/QUIC. Uses socketpair bridge so existing bufferevent-based code (msg.c, control.c, login.c) works unchanged. - config: new 'protocol' option (tcp/quic) and 'quic_bind_port' for specifying frps's QUIC listening port. - control.c: connect_server() and init_server_connection() now route through QUIC when protocol=quic. Work connections also use QUIC. - CMakeLists.txt: quic_client_transport.c always compiled (has stubs when HAVE_NGTCP2 is not defined). Usage: [common] protocol = quic quic_bind_port = 7000 server_addr = 1.2.3.4 server_port = 7000 Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
- Document wolfSSL as default TLS backend with OpenSSL fallback - Add build options table (USE_WOLFSSL, ENABLE_QUIC, DEBUG) - Add QUIC transport configuration section with frps and xfrpc examples - Add TLS backend explanation section - Update feature compatibility table with quic transport Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
The ngtcp2 crypto backend (wolfssl/openssl) is now auto-detected based on which library is actually installed, not tied to the general TLS backend choice. This fixes the build when ngtcp2 was built with wolfssl but USE_WOLFSSL=OFF is passed to cmake. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Critical fixes: - crypto.c: fix memory leak in encrypt_data/decrypt_data error paths - crypto.c: add NULL check in init_main_decoder - control.c: fix reconnect_timer event leak (store in main_ctl) - control.c: fix event object leak in clear_main_control (add event_free) - control.c: fix hash table dangling pointer (use del_proxy_client_by_stream_id) - control.c: fix partial frame data hang (add set_cur_stream before break) High/Medium fixes: - config.c: add password validation in add_user_and_set_password - config.c: replace exit(0) with exit(EXIT_FAILURE) on error paths - config.c: fix SET_STRING_VALUE macro to free old value before strdup - config.c: remove redundant field initialization in new_proxy_service - config.c: free protocol field in free_common_config - control.c: prefer system DNS with OpenWrt-aware resolv.conf search - control.h: remove duplicate function declarations - client.c: harden xdpi_engine SSH/HTTP/RDP protocol detection Low fixes: - client.c/xfrpc.c: remove duplicate #include directives - tcpmux.c: replace GCC extensions with C11 stdatomic.h Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
- control.c: fix init_main_control() reentry resource leak (use close_main_control) - control.c: set main_ctl=NULL after free in TLS/DNS failure paths - control.c: reset handle_tcp_mux static parser state on reconnect - client.c: improve xdpi HTTP validation loop readability (i+5 < len) Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Fix critical issues preventing xfrpc from establishing working QUIC connections with frps (quic-go based server): 1. TLS certificate loading for QUIC SSL_CTX: The QUIC client created its own SSL_CTX but never loaded CA, client cert, or private key from config. When frps requires mTLS (transport.tls.force=true), the handshake would succeed at the QUIC level but the server would immediately close the connection. Added tls_get_ca_file/cert_file/key_file() getters and load certs into the QUIC SSL_CTX using the same SSL library API (wolfSSL or OpenSSL) that created the context. 2. Open QUIC bidi stream before sending data: ngtcp2 requires streams to be explicitly opened via ngtcp2_conn_open_bidi_stream() before writev_stream can target them. Without this, ERR_STREAM_NOT_FOUND (-224) was returned and the connection entered ERR_DRAINING state immediately after handshake. 3. Disable tcp_mux for QUIC protocol: QUIC already provides native stream multiplexing. When tcp_mux=1 (the default), the client sent tmux-framed messages on the QUIC stream, but frps expected raw frp protocol messages. This caused the server to fail parsing and close the connection. Now tcp_mux is automatically disabled when protocol=quic. 4. Handle BEV_EVENT_CONNECTED for QUIC connections: quic_connect_to_server() returns an already-connected bufferevent (backed by socketpair). libevent does NOT fire BEV_EVENT_CONNECTED for these, so login() and proxy registration were never triggered. Added manual handle_connection_success() call for QUIC in both start_base_connect() (control connection) and init_direct_client() (work connections). 5. QUIC work stream support: Instead of creating a new QUIC connection for each work connection (which caused reentrant event_base_loop and handshake timeouts), work connections now open additional bidi streams on the existing QUIC connection via quic_open_work_stream(). Data is relayed through per-stream socketpairs with proper stream ID routing in qc_recv_stream(). 6. ERR_DRAINING handling: Added draining state checks in qc_write_udp() and qc_sp_read_cb() to prevent infinite error loops when the connection enters the draining state. 7. Port check in connect_server(): connect_server() matched QUIC work connections by IP only, which incorrectly routed local service connections (e.g., 127.0.0.1:2222) through QUIC. Added port comparison to only match frps connections. Remaining known issue: frps server panics with 'send on closed channel' when QUIC control stream and work stream share the same connection. This appears to be a server-side race condition in the QUIC control lifecycle (stream 0 read EOF triggers control teardown while work stream 4 registration is in progress). Requires frps-side fix. Testing: verified with frps (quic-go) + TLS mTLS on localhost. QUIC handshake completes, login succeeds, proxy registers, and work stream opens. End-to-end data forwarding blocked by server panic. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
…op reentrancy
Problem:
quic_connect_to_server() used a blocking while+event_base_loop()
polling loop for the QUIC handshake. This caused 'reentrant
invocation' warnings when called from the reconnect path
(reconnect_timer_cb -> run_control -> quic_connect_to_server),
since the timer callback runs inside an already-dispatching
event loop. The reentrant calls could corrupt libevent state
and cause unpredictable behavior.
Fix:
Make the QUIC handshake fully asynchronous:
- quic_connect_to_server() now returns immediately (int) after
registering UDP read + handshake timer events on the base.
- A new qc_handshake_timer_cb (10ms repeating) polls ngtcp2
for handshake completion without calling event_base_loop().
- When handshake completes, the timer opens stream 0 and invokes
a quic_handshake_cb callback with the resulting bev.
- control.c's quic_connect_done_cb handles bev setup + login
trigger, called from the timer callback context.
Results:
- Reentrant warnings: hundreds -> 0 (initial) / 2 (reconnect edge)
- ERR_DRAINING infinite loop: eliminated (clean handling in timer)
- Handshake succeeds reliably with retry
- Login + proxy registration works end-to-end
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Root cause: The QUIC handshake timer (10ms repeating) called qc_write_udp() between packet processing and handshake_confirmed callback. ngtcp2 returned ERR_DRAINING from writev_stream() because the handshake wasn't confirmed yet. The old code set draining=1 unconditionally, permanently killing the connection. Fixes: 1. qc_write_udp: Don't set draining=1 on ERR_DRAINING — just return -1 and let callers decide. Transient ERR_DRAINING during handshake is not fatal. 2. qc_handshake_timer_cb: If draining=1 but handshake_confirmed=0, treat as transient — reset draining=0 and re-arm timer. Only treat as fatal when handshake is confirmed or done. 3. qc_handshake_timer_cb: Stop and free the handshake timer once handshake_done=1. Previously the timer kept firing every 10ms calling qc_write_udp() after the handshake completed, which could interfere with the established connection. 4. Add handshake_confirmed callback to ngtcp2 callbacks struct. This is required for the client to properly confirm the QUIC v1 handshake with the server. Verified: QUIC handshake now succeeds consistently (both TLS and mTLS). Login + proxy registration works. Remaining frps RegisterWorkConn panic is a server-side issue. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Root cause: quic-go (frps) sends LoginResp as a STREAM frame on stream 0 immediately after the QUIC handshake completes. If stream 0 is not registered in ngtcp2 by the time the STREAM frame arrives, ngtcp2 silently drops it and recv_stream_data never fires. The previous code opened stream 0 in the handshake timer callback, which fires AFTER handshake_confirmed. But the server's STREAM frame (LoginResp) arrives between handshake_completed and handshake_confirmed, so it was always dropped. Fix: Open bidi stream 0 in the handshake_completed callback, which fires before the server sends LoginResp. This ensures stream 0 is registered in ngtcp2 when the STREAM frame arrives. Also: - Initialize stream_id to -1 to detect early-open - Skip duplicate stream open in timer callback - Add debug logging for sp_read_cb and writev_stream - Remove temporary fprintf debug statements Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
… fix Test infrastructure (test/e2e/): - tcp_echo_server.py: Python TCP echo server for tunnel verification - test_proxy.py: Automated test runner for tcp, mux, quic, tls scenarios - configs/: frps + xfrpc configs for each scenario (different ports) - certs/: self-signed TLS certs for the tls scenario Results: tcp PASS, mux PASS, tls PASS, quic FAIL (known work stream FIN issue between ngtcp2 and quic-go — needs packet-level investigation) Code fixes: - Add quic_work_stream_send_initial() to send NewWorkConn directly on QUIC wire, bypassing socketpair/event-loop (frps FIN'd stream before bev-flushed data arrived) - send_msg_frp_server: for QUIC work streams, write directly to socketpair fd instead of bev buffering - Add prepare_message() forward declaration
Root cause: sp_read_cb read ALL available data from the socketpair in one call, but ngtcp2_conn_writev_stream could only fit ~1200 bytes into a single QUIC packet. The unconsumed remainder was lost, causing data truncation for payloads >1 QUIC packet (~1400 bytes). Fix: - Track pdatalen from ngtcp2_conn_writev_stream to know how many bytes were actually consumed - Stash unconsumed data in out_buf - On next sp_read_cb invocation, flush out_buf before reading new data - Use event_active(read_ev) to re-trigger sp_read_cb when out_buf has remaining data after a partial send - Clean up read_ev in FIN and stream_close handlers Also: - Add quic_interop_test.py for standalone ngtcp2↔quic-go echo testing - Remove stale QUIC warning from test_proxy.py scenario description - All 4 e2e scenarios now pass: tcp, mux, quic, tls
Remove debug logs that were added during QUIC interop debugging sessions: quic_client_transport.c: - Remove hex dump of received UDP packets and stream data - Remove verbose per-packet state dumps (read_pkt, sp_read_cb, sp_writev) - Remove entry/exit traces (qc_client_initial, recv_stream_data ENTER) - Remove internal buffer state logging (control sp flush) - Remove noisy per-write 'sending N bytes' logs control.c: - Remove hex dump of encrypted/decrypted message content ([ENC_MSG] IV, enc_data) - Remove per-message tracing ([FRPS_MSG], [CTRL_WORK], [RECV_CB]) - Remove raw message content logging (Message content: ...) - Remove login request length logging - Remove [SEND_ENC] hex dump of outgoing encrypted messages - Remove connection status change logging crypto.c (SECURITY): - Remove token/salt logging in encrypt_key (leaked auth credentials) - Remove derived_key hex dump (leaked key material) - Remove key/iv logging in encrypt_data and decrypt_data Keep operational logs: connection events, errors, warnings, stream lifecycle, heartbeat, handshake completion, QUIC transport state changes. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
QUIC transport maturity release: - Full QUIC transport support (ngtcp2 + quic-go interop) - Work stream data relay fix for large payloads - IV-per-message protocol fix for frps compatibility - Null-termination safety fixes (4 crash sites) - Crypto context reset on reconnect - Stream lifecycle management (FIN/close handling) - E2E test suite (tcp/mux/quic/tls all pass) - Debug logging cleanup (removed leaked credentials) Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
…compile On OpenWrt, both system OpenSSL (/usr/include/openssl/ssl.h) and wolfSSL compat headers (/usr/include/wolfssl/openssl/ssl.h) exist. Without the wolfssl/ prefix in the include path, tls.c picks up the real OpenSSL header, so SSL_CTX_free is not macro-expanded to wolfSSL_CTX_free — causing an unresolved symbol at link time. Fix: include_directories(BEFORE /wolfssl) so that <openssl/ssl.h> resolves to wolfSSL's compat header first. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
…tion - Add ssl_compat.h conditional include wrapper for wolfSSL/OpenSSL - Fix CMakeLists.txt to link both wolfSSL and OpenSSL libraries - Rename DATA enum to TMUX_DATA to avoid wolfSSL oid_sum.h conflict - Fix corrupted new_work_connection() #ifdef HAVE_NGTCP2 structure - Fix IV protocol (iv_sent flag) for frps golib crypto interop - Fix null-termination in msg_data_to_json() helper (4 call sites) - Fix QUIC work stream FIN and partial write handling - Remove excessive debug logging - Bump version to 5.06.954 Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
wolfSSL's EVP compatibility layer is incomplete — it lacks EVP_aes_128_cfb128 required by frp's AES-128-CFB encryption. ssl_compat.h now always includes real OpenSSL headers since CMakeLists.txt already links both wolfSSL and OpenSSL when USE_WOLFSSL is enabled. Bump version to 5.06.955. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
ngtcp2's OpenSSL crypto backend library is named libngtcp2_crypto_ossl, not libngtcp2_crypto_openssl. This caused QUIC detection to fail in OpenWrt cross-compilation when using OpenSSL as the ngtcp2 crypto backend. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
ngtcp2's OpenSSL crypto backend library is named libngtcp2_crypto_ossl (not openssl). Update CMakeLists.txt to find the correct library name and add USE_NGTCP2_OSSL define. Fix header includes and API calls in quic_transport.c and quic_client_transport.c to use ossl naming. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
…ansport
- Add missing qc->base and qc->udp_read_ev initialization that was
lost during previous edits
- Add ngtcp2_crypto_ossl_configure_client_session() call and
SSL_set_app_data() for the ossl crypto backend
- Add missing openssl/rand.h include in quic_transport.c for RAND_bytes
- Replace deprecated ngtcp2_crypto_openssl_quic_method with
ngtcp2_crypto_ossl_configure_{client,server}_session in quic_transport.c
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
When both wolfSSL and OpenSSL are linked (QUIC enabled), SSL_CTX_new resolves to wolfSSL's version because it appears first in the link order. But libevent's bufferevent_openssl_socket_new uses real OpenSSL types, causing TLS handshake failures. Put OpenSSL first so the real SSL_CTX_new is used. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
There was a problem hiding this comment.
Code Review
This pull request introduces major enhancements to the xfrpc client, including the integration of wolfSSL and ngtcp2 for QUIC transport support, NAT hole punching (XTCP) for P2P traversal, TCP/HTTP health checks, Unix Domain Socket (UDS) proxying, and SIGHUP-triggered configuration hot-reloading. The review identified several critical and high-severity issues across the new transport and proxy implementations: potential data truncation and protocol corruption due to unhandled partial writes or EAGAIN when writing directly to non-blocking sockets; a lack of upper-bound validation on incoming message lengths which could cause out-of-memory (OOM) conditions; unhandled partial writes in the QUIC client transport leading to high latency; leaked QUIC streams on socketpair closure; and a permanent leak of work stream slots. Additionally, potential buffer overflows from truncated Unix socket paths and unbounded memory growth in HTTP health checks were flagged.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| ssize_t w = write(fd, req_msg, total_len); | ||
| if (w < 0) | ||
| debug(LOG_ERR, "QUIC work stream write: %s", strerror(errno)); | ||
| } |
There was a problem hiding this comment.
Writing directly to a non-blocking socket using write without handling partial writes or EAGAIN can lead to silent data truncation and protocol corruption. If the socket buffer is full, write will return EAGAIN or write fewer bytes than requested, and the unwritten bytes will be silently discarded. Consider using a loop to handle partial writes or utilizing evbuffer / bufferevent APIs to manage the buffering safely.
| int data_len = (int)msg_hton(msg->length); | ||
| char *json_str = malloc(data_len + 1); | ||
| if (!json_str) return; | ||
| memcpy(json_str, msg->data, data_len); | ||
| json_str[data_len] = '\0'; |
There was a problem hiding this comment.
Casting msg->length (which is received from the remote server) to a signed int without any upper-bound validation can lead to integer overflow or out-of-memory (OOM) denial of service. If a malformed or malicious packet specifies a very large length, malloc might fail or, worse, lead to buffer overflows if signed/unsigned mismatches occur in subsequent operations. A sanity check should be added to ensure the length is within a reasonable limit (e.g., < 10MB).
uint32_t data_len = msg_hton(msg->length);
if (data_len > 10 * 1024 * 1024) {
debug(LOG_ERR, "Received message length too large: %u", data_len);
return;
}
char *json_str = malloc(data_len + 1);
if (!json_str) return;
memcpy(json_str, msg->data, data_len);
json_str[data_len] = '\0';| size_t before = evbuffer_get_length(qc->quic_out_buf); | ||
| int n = evbuffer_write(qc->quic_out_buf, qc->sp_fd); | ||
| debug(LOG_DEBUG, "QUIC client: sp flush %d bytes (buf %zu -> %zu)", | ||
| n, before, evbuffer_get_length(qc->quic_out_buf)); | ||
| if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) | ||
| debug(LOG_ERR, "QUIC client: sp write err: %s", strerror(errno)); |
There was a problem hiding this comment.
evbuffer_write can perform a partial write if the socket's write buffer is full. When this happens, the remaining data stays in qc->quic_out_buf. However, there is no write event registered on qc->sp_fd to resume flushing when the socket becomes writeable again. The remaining data will remain stuck until the next UDP read event triggers another flush, causing high latency or hangs.
| ssize_t n = read(fd, buf, sizeof(buf)); | ||
| if (n <= 0) { if (n < 0 && errno == EAGAIN) return; return; } |
There was a problem hiding this comment.
If read returns 0 (EOF) or a negative value (excluding EAGAIN/EWOULDBLOCK), it indicates that the local side of the socketpair has closed. Currently, the callback simply returns without notifying the remote QUIC peer, leading to a leaked QUIC stream. The stream should be explicitly shut down using ngtcp2_conn_shutdown_stream.
| if (qc->work_stream_count >= 64) { | ||
| debug(LOG_ERR, "QUIC: too many work streams"); | ||
| close(sp[0]); close(sp[1]); | ||
| return NULL; | ||
| } | ||
| struct work_stream_relay *relay = &qc->work_streams[qc->work_stream_count++]; | ||
| relay->stream_id = sid; | ||
| relay->sp_fd = sp[0]; | ||
| relay->out_buf = evbuffer_new(); |
There was a problem hiding this comment.
When a work stream is closed in qc_stream_close, its resources are freed, but the slot in qc->work_streams is not removed, and qc->work_stream_count is never decremented. This creates a permanent leak of stream slots, meaning a single QUIC connection can only ever handle 64 work streams before failing with "QUIC: too many work streams". Reusing empty slots (where sp_fd == -1) resolves this limitation.
struct work_stream_relay *relay = NULL;
for (int i = 0; i < qc->work_stream_count; i++) {
if (qc->work_streams[i].sp_fd < 0) {
relay = &qc->work_streams[i];
break;
}
}
if (!relay) {
if (qc->work_stream_count >= 64) {
debug(LOG_ERR, "QUIC: too many work streams");
close(sp[0]); close(sp[1]);
return NULL;
}
relay = &qc->work_streams[qc->work_stream_count++];
}
relay->stream_id = sid;
relay->sp_fd = sp[0];
relay->out_buf = evbuffer_new();| struct sockaddr_un sun; | ||
| memset(&sun, 0, sizeof(sun)); | ||
| sun.sun_family = AF_UNIX; | ||
| strncpy(sun.sun_path, unix_path, sizeof(sun.sun_path) - 1); |
There was a problem hiding this comment.
If unix_path exceeds sizeof(sun.sun_path) - 1 (typically 107 bytes on Linux), strncpy will truncate the path. Connecting to a truncated path will fail or connect to the wrong socket. A length check should be added to reject paths that are too long.
if (strlen(unix_path) >= sizeof(sun.sun_path)) {
debug(LOG_ERR, "Unix socket path too long: %s", unix_path);
close(fd);
return NULL;
}
struct sockaddr_un sun;
memset(&sun, 0, sizeof(sun));
sun.sun_family = AF_UNIX;
strncpy(sun.sun_path, unix_path, sizeof(sun.sun_path) - 1);| struct evbuffer *input = bufferevent_get_input(bev); | ||
|
|
||
| /* Wait until we have the full status line (terminated by \r\n). | ||
| * In high-latency conditions the status line may arrive across | ||
| * multiple TCP segments; parsing prematurely would cause false | ||
| * health-check failures. */ | ||
| struct evbuffer_ptr crlf = evbuffer_search(input, "\r\n", 2, NULL); |
There was a problem hiding this comment.
If the HTTP server sends a very long status line or keeps sending data without a \r\n, the input buffer will grow indefinitely, leading to high memory usage or OOM. A maximum limit (e.g., 4096 bytes) should be enforced on the input buffer length before failing the health check.
struct evbuffer *input = bufferevent_get_input(bev);
if (evbuffer_get_length(input) > 4096) {
debug(LOG_WARNING, "Health check [%s]: HTTP status line too long", entry->proxy_name);
hc_finish_check(entry, 0);
return;
}
struct evbuffer_ptr crlf = evbuffer_search(input, "\r\n", 2, NULL);Add native TOML parsing for frp-compatible configuration files. xfrpc now supports both INI (.ini) and TOML (.toml) config formats, auto-detected by file extension via -c option. TOML features supported: - Top-level key-value pairs (serverAddr, serverPort, etc.) - Dotted keys for nested config (auth.token, transport.tls.enable) - [[proxies]] array of tables for proxy definitions - [[visitors]] array of tables for visitor definitions - Inline arrays (customDomains = ["a", "b"]) - String, integer, and boolean value types - Comments (# ...) and blank lines Key mapping from frp TOML to xfrpc internal fields: - serverAddr -> server_addr, serverPort -> server_port - auth.token -> auth_token - transport.tls.enable -> tls_enable - proxies[].secretKey -> sk, proxies[].localIP -> local_ip - visitors[].serverName -> server_name, visitors[].bindAddr -> bind_addr New files: - toml_parser.c/h: Lightweight TOML parser (no external dependencies) - xfrpc.toml: STCP proxy + visitor example config - xfrpc_min.toml: Minimal TCP proxy example - xfrpc_full.toml: Full-featured example with HTTP, TCP, STCP Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
The init script outputs serviceType for TCP/TCPMux proxies but the TOML load_toml_proxies() function never read it, causing the field to be silently ignored. Add toml_get for 'serviceType' mapped to convert_service_type(). Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Replace the hand-written toml_parser.c/h with a thin wrapper around cktan/tomlc17 (TOML v1.1 compliant, C17, amalgamated). - Vendor tomlc17.h + tomlc17.c under vendor/tomlc17/ - Rewrite toml_parser.c/h as a compatibility wrapper (xfrpc_toml_* functions with old-name macros for config.c) - Update config.c: use void* for section handles, remove internal struct access - Update CMakeLists.txt: add vendor/tomlc17/tomlc17.c Benefits: full TOML v1.1 compliance, proper dotted-key support, no hand-maintained parser code. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
The scratch buffer was replaced by thread-local storage in xfrpc_toml_get(). Remove the dead field to save 1KB per doc. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Implement AES-128-CFB encryption and Snappy compression for proxy work connections, compatible with frp's use_encryption/use_compression protocol. Encryption (AES-128-CFB): - Key derived from auth.token via PBKDF2(salt='crypto', iter=64, SHA1) - Random 16-byte IV prepended to first write - Uses OpenSSL EVP API Compression (Snappy): - Applied before encryption on send, after decryption on recv - Uses snappy-c library - Conditionally compiled with HAS_SNAPPY Data flow (client→server): compress → encrypt → send Data flow (server→client): recv → decrypt → decompress Files added: - crypto_stream.c/h: AES-128-CFB + snappy wrapper functions Files modified: - client.h: add crypto/compression context fields to proxy_client - client.c: initialize crypto from proxy_service, cleanup on free - proxy_tcp.c: encrypt/decrypt data in c2s/s2c relay callbacks - CMakeLists.txt: add crypto_stream.c, find snappy library Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
When HAS_SNAPPY is not defined (e.g. OpenWrt), provide non-inline stub implementations of snappy_ctx_new/free/compress/decompress in crypto_stream.c so linking succeeds. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
zlib is already a dependency of xfrpc, available on all platforms including OpenWrt. No need for external snappy library. Wire format: [4-byte BE compressed_len][zlib compressed data] Note: not compatible with frp's snappy compression format. use_compression works between xfrpc instances only. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Vendor snappy-c (Linux kernel version, ~1600 lines) into vendor/snappy/ so use_compression produces snappy-framed output compatible with frp's golang implementation. No external snappy library needed - compiles from source on all platforms including OpenWrt. Replaces the previous zlib compression approach. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Implement OAuth2 client_credentials grant for OIDC authentication, compatible with frp's auth.oidc.* configuration. New TOML config fields: - auth.method = "oidc" (instead of "token") - auth.oidc.clientID - auth.oidc.clientSecret - auth.oidc.audience - auth.oidc.scope - auth.oidc.tokenEndpointURL - auth.oidc.trustedCaFile - auth.oidc.insecureSkipVerify Implementation: - oidc_auth.c/h: HTTP(S) POST to token endpoint, JSON response parsing - Uses raw sockets + OpenSSL (no external HTTP library dependency) - msg.c: login message uses OIDC access_token as privilege_key - config.c: TOML + INI parser support for all OIDC fields - xfrpc.init: UCI → TOML generation for OIDC config Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Compatible with frp's requestHeaders.set.* and responseHeaders.set.* TOML configuration. Headers are sent to frps in the NewProxy message as 'headers' and 'response_headers' JSON maps. - config.c: TOML parser reads requestHeaders.set/responseHeaders.set via new xfrpc_toml_get_table_pairs() wrapper - config.c: INI parser reads request_headers/response_headers - msg.c: JSON marshaling as 'headers'/'response_headers' maps - toml_parser.c/h: new xfrpc_toml_get_table_pairs() for table iteration - xfrpc.init: UCI options request_headers/response_headers (comma-separated key=value format) Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Read startTime and endTime from [[proxies]] TOML sections, enabling time-based proxy management through TOML config files. Previously only supported via INI config (config.c common_handler). Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Remove IOD (I/O Direct) proxy: - Remove from valid_types[] in config.c - Remove IOD validation in validate_proxy() - Remove handle_iod() from init script Remove FTP proxy: - Remove proxy_ftp.c from build (CMakeLists.txt) - Remove ftp_pasv struct and FTP callbacks from proxy.h - Remove is_ftp_proxy() from client.c/h - Remove FTP dispatch in setup_proxy_callbacks() - Remove new_ftp_data_proxy_service() from config.c - Remove get_ftp_data_proxy_name() from config.c/h - Remove handle_ftp_configuration() from control.c - Remove FTP-specific msg marshaling from msg.c - Remove remote_data_port from proxy struct - Remove ftp_cfg_proxy_name from proxy_service struct These proxy types were unused and added complexity. Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
Major changes since 5.06.956: - TOML configuration support (frp-compatible) via tomlc17 - OIDC authentication (auth.oidc.*) - AES-128-CFB use_encryption (frp-compatible) - Snappy use_compression (frp-compatible, vendored) - HTTP requestHeaders/responseHeaders support - Time-based proxy management (startTime/endTime) - serviceType XDPI classification - Removed IOD and FTP proxy types - All proxy fields exposed via OpenWrt UCI init script Signed-off-by: Dengfeng Liu <liudf0716@gmail.com>
No description provided.