From 777d65020910f018234e23c70b2941091b05eb5d Mon Sep 17 00:00:00 2001 From: Sourav Kalal Date: Mon, 2 Feb 2026 13:36:21 +0530 Subject: [PATCH 01/58] updated buffer size --- Windows/src/ProxyBridge.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Windows/src/ProxyBridge.c b/Windows/src/ProxyBridge.c index 1bf6899..9f2a4c6 100644 --- a/Windows/src/ProxyBridge.c +++ b/Windows/src/ProxyBridge.c @@ -12,14 +12,15 @@ #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "ws2_32.lib") -#define MAXBUF 0xFFFF +#define MAXBUF 1500 // Optimized for typical MTU (WinDivert performance recommendation) +#define PACKET_BATCH_SIZE 64 // Batch mode: process multiple packets per context switch #define LOCAL_PROXY_PORT 34010 #define LOCAL_UDP_RELAY_PORT 34011 // its running UDP port still make sure to not run on same port as TCP, opening same port and tcp and udp cause issue and handling port at relay server response injection #define MAX_PROCESS_NAME 256 #define VERSION "3.1.0" #define PID_CACHE_SIZE 1024 #define PID_CACHE_TTL_MS 1000 -#define NUM_PACKET_THREADS 4 +#define NUM_PACKET_THREADS 4 // Multi-threading for parallel packet processing #define CONNECTION_HASH_SIZE 256 #define SOCKS5_BUFFER_SIZE 1024 #define HTTP_BUFFER_SIZE 1024 @@ -304,7 +305,7 @@ static DWORD WINAPI packet_processor(LPVOID arg) UINT32 dest_ip = ip_header->DstAddr; UINT16 dest_port = ntohs(udp_header->DstPort); - // if no rule configuree all connection direct with no checks avoid unwanted memory and pocessing whcich could delay + // if no rule configured all connection direct with no checks avoid unwanted memory and processing which could delay if (!g_has_active_rules && g_connection_callback == NULL) { // No rules and no logging - pass through immediately (no checksum needed for unmodified packets) @@ -447,7 +448,7 @@ static DWORD WINAPI packet_processor(LPVOID arg) UINT32 orig_dest_ip = ip_header->DstAddr; UINT16 orig_dest_port = ntohs(tcp_header->DstPort); - // avoid rule pocess and packet process if no rules + // avoid rule process and packet process if no rules if (!g_has_active_rules && g_connection_callback == NULL) { WinDivertSend(windivert_handle, packet, packet_len, NULL, &addr); @@ -518,14 +519,14 @@ static DWORD WINAPI packet_processor(LPVOID arg) continue; } else if (action == RULE_ACTION_PROXY) - { - add_connection(src_port, src_ip, orig_dest_ip, orig_dest_port); + { + add_connection(src_port, src_ip, orig_dest_ip, orig_dest_port); - UINT32 temp_addr = ip_header->DstAddr; - tcp_header->DstPort = htons(g_local_relay_port); - ip_header->DstAddr = ip_header->SrcAddr; - ip_header->SrcAddr = temp_addr; - addr.Outbound = FALSE; + UINT32 temp_addr = ip_header->DstAddr; + tcp_header->DstPort = htons(g_local_relay_port); + ip_header->DstAddr = ip_header->SrcAddr; + ip_header->SrcAddr = temp_addr; + addr.Outbound = FALSE; } } } @@ -2738,8 +2739,9 @@ PROXYBRIDGE_API BOOL ProxyBridge_Start(void) return FALSE; } - WinDivertSetParam(windivert_handle, WINDIVERT_PARAM_QUEUE_LENGTH, 8192); - WinDivertSetParam(windivert_handle, WINDIVERT_PARAM_QUEUE_TIME, 8); // 8ms for low latency + // Performance optimization: larger queue to prevent packet drops under heavy load (>= 1Gbps) + WinDivertSetParam(windivert_handle, WINDIVERT_PARAM_QUEUE_LENGTH, (UINT64)16384); // Doubled queue length + WinDivertSetParam(windivert_handle, WINDIVERT_PARAM_QUEUE_TIME, (UINT64)2000); // 2 seconds max queue time for (int i = 0; i < NUM_PACKET_THREADS; i++) { From a9935810663896a91af31967ee0abfd219ec66cd Mon Sep 17 00:00:00 2001 From: Sourav Kalal Date: Tue, 3 Feb 2026 13:23:20 +0530 Subject: [PATCH 02/58] updated workflow --- .github/workflows/release-windows.yml | 6 ++++-- Windows/installer/ProxyBridge.nsi | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index f3b1dfc..cfd1307 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -2,11 +2,13 @@ name: Release ProxyBridge Windows on: release: - types: [created] + types: [published, created] + workflow_dispatch: jobs: build-and-release: runs-on: self-hosted + if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.actor == github.repository_owner) steps: - name: Checkout code @@ -15,7 +17,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '9.0.x' + dotnet-version: '10.0.x' - name: Verify WinDivert installation run: | diff --git a/Windows/installer/ProxyBridge.nsi b/Windows/installer/ProxyBridge.nsi index 1e9f3b2..2983eca 100644 --- a/Windows/installer/ProxyBridge.nsi +++ b/Windows/installer/ProxyBridge.nsi @@ -7,6 +7,16 @@ Unicode True +; Version Information +VIProductVersion "3.1.0.0" +VIAddVersionKey "ProductName" "${PRODUCT_NAME}" +VIAddVersionKey "ProductVersion" "${PRODUCT_VERSION}" +VIAddVersionKey "CompanyName" "${PRODUCT_PUBLISHER}" +VIAddVersionKey "LegalCopyright" "Copyright (c) 2026 ${PRODUCT_PUBLISHER}" +VIAddVersionKey "FileDescription" "${PRODUCT_NAME} Setup" +VIAddVersionKey "FileVersion" "${PRODUCT_VERSION}" +VIAddVersionKey "Comments" "Network Proxy Bridge Application" + !include "MUI2.nsh" SetCompressor /SOLID lzma From 1c849bdaf8aba8be0b672075a374444bfd0c8d2a Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Tue, 3 Feb 2026 15:06:51 +0530 Subject: [PATCH 03/58] Added Linux Initial implementation NOTE - Still under developent with almost no testing done if this method work with all required features same as mac and windows - Moved Local Git to public dev branch - Added Windows arch in Linux - Moved from EBPF based development to NFQUEUE - Added test CLI --- Linux/cli/Makefile | 21 + Linux/cli/main.c | 164 +++ Linux/src/Makefile | 25 + Linux/src/ProxyBridge.c | 2160 +++++++++++++++++++++++++++++++++++++++ Linux/src/ProxyBridge.h | 50 + 5 files changed, 2420 insertions(+) create mode 100644 Linux/cli/Makefile create mode 100644 Linux/cli/main.c create mode 100644 Linux/src/Makefile create mode 100644 Linux/src/ProxyBridge.c create mode 100644 Linux/src/ProxyBridge.h diff --git a/Linux/cli/Makefile b/Linux/cli/Makefile new file mode 100644 index 0000000..083a14b --- /dev/null +++ b/Linux/cli/Makefile @@ -0,0 +1,21 @@ +CC = gcc +CFLAGS = -Wall -Wextra -O2 -I../src +LDFLAGS = -L../src +LIBS = -lproxybridge -lpthread + +TARGET = proxybridge-cli +SOURCES = main.c +OBJECTS = $(SOURCES:.c=.o) + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJECTS) $(TARGET) + +.PHONY: all clean diff --git a/Linux/cli/main.c b/Linux/cli/main.c new file mode 100644 index 0000000..c3c582b --- /dev/null +++ b/Linux/cli/main.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include +#include +#include "../src/ProxyBridge.h" + +static volatile bool keep_running = true; + +static void log_callback(const char* message) +{ + printf("[LOG] %s\n", message); +} + +static void connection_callback(const char* process_name, uint32_t pid, const char* dest_ip, uint16_t dest_port, const char* proxy_info) +{ + printf("[CONN] %s (PID: %u) -> %s:%u via %s\n", + process_name, pid, dest_ip, dest_port, proxy_info); +} + +static void signal_handler(int sig) +{ + (void)sig; + keep_running = false; +} + +static void print_usage(const char* prog) +{ + printf("usage: %s [options]\n", prog); + printf("options:\n"); + printf(" -h proxy server hostname or ip (default: 127.0.0.1)\n"); + printf(" -p proxy server port (default: 1080)\n"); + printf(" -t proxy type: socks5 or http (default: socks5)\n"); + printf(" -u proxy username (optional)\n"); + printf(" -w proxy password (optional)\n"); + printf(" -d disable dns via proxy\n"); + printf(" --help show this help\n"); + printf("\nexample:\n"); + printf(" %s -h 127.0.0.1 -p 1080 -t socks5\n", prog); +} + +int main(int argc, char *argv[]) +{ + char proxy_host[256] = "192.168.1.4"; + uint16_t proxy_port = 4444; + ProxyType proxy_type = PROXY_TYPE_SOCKS5; + char username[256] = ""; + char password[256] = ""; + bool dns_via_proxy = true; + + // parse args + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--help") == 0) + { + print_usage(argv[0]); + return 0; + } + else if (strcmp(argv[i], "-h") == 0 && i + 1 < argc) + { + strncpy(proxy_host, argv[++i], sizeof(proxy_host) - 1); + } + else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) + { + proxy_port = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) + { + if (strcmp(argv[i + 1], "http") == 0) + proxy_type = PROXY_TYPE_HTTP; + else if (strcmp(argv[i + 1], "socks5") == 0) + proxy_type = PROXY_TYPE_SOCKS5; + i++; + } + else if (strcmp(argv[i], "-u") == 0 && i + 1 < argc) + { + strncpy(username, argv[++i], sizeof(username) - 1); + } + else if (strcmp(argv[i], "-w") == 0 && i + 1 < argc) + { + strncpy(password, argv[++i], sizeof(password) - 1); + } + else if (strcmp(argv[i], "-d") == 0) + { + dns_via_proxy = false; + } + } + + printf("proxybridge cli test\n"); + printf("proxy: %s://%s:%u\n", proxy_type == PROXY_TYPE_HTTP ? "http" : "socks5", + proxy_host, proxy_port); + + // setup callbacks + ProxyBridge_SetLogCallback(log_callback); + ProxyBridge_SetConnectionCallback(connection_callback); + + // configure proxy + if (!ProxyBridge_SetProxyConfig(proxy_type, proxy_host, proxy_port, + username[0] ? username : NULL, + password[0] ? password : NULL)) + { + fprintf(stderr, "failed to configure proxy\n"); + return 1; + } + + ProxyBridge_SetDnsViaProxy(dns_via_proxy); + ProxyBridge_SetTrafficLoggingEnabled(true); + + // add rule - curl tcp traffic via proxy + uint32_t rule_id = ProxyBridge_AddRule("curl", "*", "*", RULE_PROTOCOL_TCP, RULE_ACTION_PROXY); + if (rule_id == 0) + { + fprintf(stderr, "failed to add curl rule\n"); + return 1; + } + printf("added rule id %u: curl tcp traffic via proxy\n", rule_id); + + // test connection + char test_result[2048]; + printf("\ntesting proxy connection\n"); + if (ProxyBridge_TestConnection("www.google.com", 80, test_result, sizeof(test_result)) == 0) + { + printf("%s\n", test_result); + } + else + { + printf("connection test failed:\n%s\n", test_result); + return 1; + } + + // start service + printf("\nstarting proxybridge\n"); + printf("note: requires root privileges for nfqueue\n"); + + if (getuid() != 0) + { + fprintf(stderr, "error: must run as root\n"); + return 1; + } + + if (!ProxyBridge_Start()) + { + fprintf(stderr, "failed to start proxybridge\n"); + return 1; + } + + printf("proxybridge running - press ctrl+c to stop\n"); + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + // main loop + while (keep_running) + { + sleep(1); + } + + printf("\nstopping proxybridge\n"); + ProxyBridge_Stop(); + + printf("cleanup completed\n"); + return 0; +} diff --git a/Linux/src/Makefile b/Linux/src/Makefile new file mode 100644 index 0000000..d134594 --- /dev/null +++ b/Linux/src/Makefile @@ -0,0 +1,25 @@ +CC = gcc +CFLAGS = -Wall -Wextra -O2 -fPIC -D_GNU_SOURCE +LDFLAGS = -shared +LIBS = -lpthread -lnetfilter_queue -lnfnetlink + +TARGET = libproxybridge.so +SOURCES = ProxyBridge.c +OBJECTS = $(SOURCES:.c=.o) + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJECTS) $(TARGET) + +install: $(TARGET) + install -m 0755 $(TARGET) /usr/local/lib/ + ldconfig + +.PHONY: all clean install diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c new file mode 100644 index 0000000..d9a6b2f --- /dev/null +++ b/Linux/src/ProxyBridge.c @@ -0,0 +1,2160 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ProxyBridge.h" + +#define LOCAL_PROXY_PORT 34010 +#define LOCAL_UDP_RELAY_PORT 34011 +#define MAX_PROCESS_NAME 256 +#define PID_CACHE_SIZE 1024 +#define PID_CACHE_TTL_MS 1000 +#define NUM_PACKET_THREADS 4 +#define CONNECTION_HASH_SIZE 256 +#define SOCKS5_BUFFER_SIZE 1024 +#define HTTP_BUFFER_SIZE 1024 +#define LOG_BUFFER_SIZE 1024 + +typedef struct PROCESS_RULE { + uint32_t rule_id; + char process_name[MAX_PROCESS_NAME]; + char *target_hosts; + char *target_ports; + RuleProtocol protocol; + RuleAction action; + bool enabled; + struct PROCESS_RULE *next; +} PROCESS_RULE; + +#define SOCKS5_VERSION 0x05 +#define SOCKS5_CMD_CONNECT 0x01 +#define SOCKS5_CMD_UDP_ASSOCIATE 0x03 +#define SOCKS5_ATYP_IPV4 0x01 +#define SOCKS5_AUTH_NONE 0x00 + +typedef struct CONNECTION_INFO { + uint16_t src_port; + uint32_t src_ip; + uint32_t orig_dest_ip; + uint16_t orig_dest_port; + bool is_tracked; + uint64_t last_activity; + struct CONNECTION_INFO *next; +} CONNECTION_INFO; + +typedef struct { + int client_socket; + uint32_t orig_dest_ip; + uint16_t orig_dest_port; +} CONNECTION_CONFIG; + +typedef struct { + int from_socket; + int to_socket; +} TRANSFER_CONFIG; + +typedef struct LOGGED_CONNECTION { + uint32_t pid; + uint32_t dest_ip; + uint16_t dest_port; + RuleAction action; + struct LOGGED_CONNECTION *next; +} LOGGED_CONNECTION; + +typedef struct PID_CACHE_ENTRY { + uint32_t src_ip; + uint16_t src_port; + uint32_t pid; + uint64_t timestamp; + bool is_udp; + struct PID_CACHE_ENTRY *next; +} PID_CACHE_ENTRY; + +static CONNECTION_INFO *connection_hash_table[CONNECTION_HASH_SIZE] = {NULL}; +static LOGGED_CONNECTION *logged_connections = NULL; +static PROCESS_RULE *rules_list = NULL; +static uint32_t g_next_rule_id = 1; +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static struct nfq_handle *nfq_h = NULL; +static struct nfq_q_handle *nfq_qh = NULL; +static pthread_t packet_thread[NUM_PACKET_THREADS] = {0}; +static pthread_t proxy_thread = 0; +static pthread_t cleanup_thread = 0; +static PID_CACHE_ENTRY *pid_cache[PID_CACHE_SIZE] = {NULL}; +static bool g_has_active_rules = false; +static bool running = false; +static uint32_t g_current_process_id = 0; + +static bool g_traffic_logging_enabled = true; + +static char g_proxy_host[256] = ""; +static uint16_t g_proxy_port = 0; +static uint16_t g_local_relay_port = LOCAL_PROXY_PORT; +static ProxyType g_proxy_type = PROXY_TYPE_SOCKS5; +static char g_proxy_username[256] = ""; +static char g_proxy_password[256] = ""; +static bool g_dns_via_proxy = true; +static LogCallback g_log_callback = NULL; +static ConnectionCallback g_connection_callback = NULL; + +static void log_message(const char *msg, ...) +{ + if (g_log_callback == NULL) return; + char buffer[LOG_BUFFER_SIZE]; + va_list args; + va_start(args, msg); + vsnprintf(buffer, sizeof(buffer), msg, args); + va_end(args); + g_log_callback(buffer); +} + +static const char* extract_filename(const char* path) +{ + if (!path) return ""; + const char* last_slash = strrchr(path, '/'); + return last_slash ? (last_slash + 1) : path; +} + +static inline char* skip_whitespace(char *str) +{ + while (*str == ' ' || *str == '\t') + str++; + return str; +} + +static void format_ip_address(uint32_t ip, char *buffer, size_t size) +{ + snprintf(buffer, size, "%d.%d.%d.%d", + (ip >> 0) & 0xFF, (ip >> 8) & 0xFF, + (ip >> 16) & 0xFF, (ip >> 24) & 0xFF); +} + +typedef bool (*token_match_func)(const char *token, const void *data); + +static bool parse_token_list(const char *list, const char *delimiters, token_match_func match_func, const void *match_data) +{ + if (list == NULL || list[0] == '\0' || strcmp(list, "*") == 0) + return true; + + size_t len = strlen(list) + 1; + char *list_copy = malloc(len); + if (list_copy == NULL) + return false; + + strncpy(list_copy, list, len - 1); + list_copy[len - 1] = '\0'; + bool matched = false; + char *saveptr = NULL; + char *token = strtok_r(list_copy, delimiters, &saveptr); + while (token != NULL) + { + token = skip_whitespace(token); + if (match_func(token, match_data)) + { + matched = true; + break; + } + token = strtok_r(NULL, delimiters, &saveptr); + } + free(list_copy); + return matched; +} + +static void configure_tcp_socket(int sock, int bufsize, int timeout_ms) +{ + int nodelay = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)); + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)); + struct timeval timeout = {timeout_ms / 1000, (timeout_ms % 1000) * 1000}; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); +} + +static void configure_udp_socket(int sock, int bufsize, int timeout_ms) +{ + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)); + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)); + struct timeval timeout = {timeout_ms / 1000, (timeout_ms % 1000) * 1000}; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); +} + +static ssize_t send_all(int sock, const char *buf, size_t len) +{ + size_t sent = 0; + while (sent < len) { + ssize_t n = send(sock, buf + sent, len - sent, 0); + if (n < 0) return -1; + sent += n; + } + return sent; +} + +static uint64_t get_monotonic_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +static uint32_t parse_ipv4(const char *ip); +static uint32_t resolve_hostname(const char *hostname); +static int socks5_connect(int s, uint32_t dest_ip, uint16_t dest_port); +static bool match_ip_pattern(const char *pattern, uint32_t ip); +static bool match_port_pattern(const char *pattern, uint16_t port); +static bool match_ip_list(const char *ip_list, uint32_t ip); +static bool match_port_list(const char *port_list, uint16_t port); +static bool match_process_pattern(const char *pattern, const char *process_name); +static bool match_process_list(const char *process_list, const char *process_name); +static int http_connect(int s, uint32_t dest_ip, uint16_t dest_port); +static void* local_proxy_server(void *arg); +static void* connection_handler(void *arg); +static void* transfer_handler(void *arg); +static void* packet_processor(void *arg); +static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_port, bool is_udp); +static bool get_process_name_from_pid(uint32_t pid, char *name, size_t name_size); +static RuleAction match_rule(const char *process_name, uint32_t dest_ip, uint16_t dest_port, bool is_udp); +static RuleAction check_process_rule(uint32_t src_ip, uint16_t src_port, uint32_t dest_ip, uint16_t dest_port, bool is_udp, uint32_t *out_pid); +static void add_connection(uint16_t src_port, uint32_t src_ip, uint32_t dest_ip, uint16_t dest_port); +static bool get_connection(uint16_t src_port, uint32_t *dest_ip, uint16_t *dest_port); +static bool is_connection_tracked(uint16_t src_port); +static void remove_connection(uint16_t src_port); +static void cleanup_stale_connections(void); +static bool is_connection_already_logged(uint32_t pid, uint32_t dest_ip, uint16_t dest_port, RuleAction action); +static void add_logged_connection(uint32_t pid, uint32_t dest_ip, uint16_t dest_port, RuleAction action); +static void clear_logged_connections(void); +static bool is_broadcast_or_multicast(uint32_t ip); +static uint32_t get_cached_pid(uint32_t src_ip, uint16_t src_port, bool is_udp); +static void cache_pid(uint32_t src_ip, uint16_t src_port, uint32_t pid, bool is_udp); +static void clear_pid_cache(void); +static void update_has_active_rules(void); + +// fast pid lookup using netlink sock_diag +static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_port, bool is_udp) +{ + uint32_t cached_pid = get_cached_pid(src_ip, src_port, is_udp); + if (cached_pid != 0) + return cached_pid; + + int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_SOCK_DIAG); + if (fd < 0) { + return 0; + } + + struct { + struct nlmsghdr nlh; + struct inet_diag_req_v2 r; + } req; + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY; + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.r.sdiag_family = AF_INET; + req.r.sdiag_protocol = is_udp ? IPPROTO_UDP : IPPROTO_TCP; + req.r.idiag_states = is_udp ? (1 << TCP_ESTABLISHED) : -1; + req.r.idiag_ext = 0; + + struct sockaddr_nl sa; + memset(&sa, 0, sizeof(sa)); + sa.nl_family = AF_NETLINK; + + if (sendto(fd, &req, sizeof(req), 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + close(fd); + return 0; + } + + uint32_t pid = 0; + unsigned long target_inode = 0; + int matches_found = 0; + char buf[8192]; + struct iovec iov = {buf, sizeof(buf)}; + struct msghdr msg = {&sa, sizeof(sa), &iov, 1, NULL, 0, 0}; + + while (1) { + ssize_t len = recvmsg(fd, &msg, 0); + if (len < 0) { + break; + } + if (len == 0) break; + + struct nlmsghdr *h = (struct nlmsghdr *)buf; + while (NLMSG_OK(h, len)) { + if (h->nlmsg_type == NLMSG_DONE) { + goto done; + } + if (h->nlmsg_type == NLMSG_ERROR) { + goto done; + } + if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY) { + struct inet_diag_msg *r = NLMSG_DATA(h); + + // match src ip and port + if (r->id.idiag_src[0] == src_ip && ntohs(r->id.idiag_sport) == src_port) { + matches_found++; + target_inode = r->idiag_inode; + + // read /proc/net/{tcp,udp} to map inode to pid + char proc_file[64]; + snprintf(proc_file, sizeof(proc_file), "/proc/net/%s", is_udp ? "udp" : "tcp"); + FILE *fp = fopen(proc_file, "r"); + if (fp) { + char line[512]; + (void)fgets(line, sizeof(line), fp); // skip header + while (fgets(line, sizeof(line), fp)) { + unsigned long inode; + int uid_val, pid_val; + if (sscanf(line, "%*d: %*X:%*X %*X:%*X %*X %*X:%*X %*X:%*X %*X %d %*d %lu", + &uid_val, &inode) == 2) { + if (inode == target_inode) { + // now find PID from /proc/*/fd/* with this inode + // this is expensive but necessary + DIR *proc_dir = opendir("/proc"); + if (proc_dir) { + struct dirent *proc_entry; + while ((proc_entry = readdir(proc_dir)) != NULL) { + if (!isdigit(proc_entry->d_name[0])) continue; + + char fd_path[256]; + snprintf(fd_path, sizeof(fd_path), "/proc/%s/fd", proc_entry->d_name); + DIR *fd_dir = opendir(fd_path); + if (fd_dir) { + struct dirent *fd_entry; + while ((fd_entry = readdir(fd_dir)) != NULL) { + if (fd_entry->d_name[0] == '.') continue; + + char link_path[512], link_target[512]; + snprintf(link_path, sizeof(link_path), "/proc/%s/fd/%s", + proc_entry->d_name, fd_entry->d_name); + ssize_t link_len = readlink(link_path, link_target, sizeof(link_target)-1); + if (link_len > 0) { + link_target[link_len] = '\0'; + char expected[64]; + snprintf(expected, sizeof(expected), "socket:[%lu]", target_inode); + if (strcmp(link_target, expected) == 0) { + pid = atoi(proc_entry->d_name); + closedir(fd_dir); + closedir(proc_dir); + fclose(fp); + goto done; + } + } + } + closedir(fd_dir); + } + } + closedir(proc_dir); + } + fclose(fp); + goto done; + } + } + } + fclose(fp); + } + goto done; + } + } + h = NLMSG_NEXT(h, len); + } + } + +done: + close(fd); + if (pid != 0) + cache_pid(src_ip, src_port, pid, is_udp); + return pid; +} + +static bool get_process_name_from_pid(uint32_t pid, char *name, size_t name_size) +{ + if (pid == 0) + return false; + + char path[64]; + snprintf(path, sizeof(path), "/proc/%u/exe", pid); + + ssize_t len = readlink(path, name, name_size - 1); + if (len < 0) + return false; + + name[len] = '\0'; + return true; +} + +static bool match_ip_pattern(const char *pattern, uint32_t ip) +{ + if (pattern == NULL || strcmp(pattern, "*") == 0) + return true; + + unsigned char ip_octets[4]; + ip_octets[0] = (ip >> 0) & 0xFF; + ip_octets[1] = (ip >> 8) & 0xFF; + ip_octets[2] = (ip >> 16) & 0xFF; + ip_octets[3] = (ip >> 24) & 0xFF; + + char pattern_copy[256]; + strncpy(pattern_copy, pattern, sizeof(pattern_copy) - 1); + pattern_copy[sizeof(pattern_copy) - 1] = '\0'; + + char pattern_octets[4][16]; + int octet_count = 0; + int char_idx = 0; + + for (size_t i = 0; i <= strlen(pattern_copy) && octet_count < 4; i++) + { + if (pattern_copy[i] == '.' || pattern_copy[i] == '\0') + { + pattern_octets[octet_count][char_idx] = '\0'; + octet_count++; + char_idx = 0; + if (pattern_copy[i] == '\0') + break; + } + else + { + if (char_idx < 15) + pattern_octets[octet_count][char_idx++] = pattern_copy[i]; + } + } + + if (octet_count != 4) + return false; + + for (int i = 0; i < 4; i++) + { + if (strcmp(pattern_octets[i], "*") == 0) + continue; + int pattern_val = atoi(pattern_octets[i]); + if (pattern_val != ip_octets[i]) + return false; + } + return true; +} + +static bool match_port_pattern(const char *pattern, uint16_t port) +{ + if (pattern == NULL || strcmp(pattern, "*") == 0) + return true; + + char *dash = strchr(pattern, '-'); + if (dash != NULL) + { + int start_port = atoi(pattern); + int end_port = atoi(dash + 1); + return (port >= start_port && port <= end_port); + } + + return (port == atoi(pattern)); +} + +static bool ip_match_wrapper(const char *token, const void *data) +{ + return match_ip_pattern(token, *(const uint32_t*)data); +} + +static bool match_ip_list(const char *ip_list, uint32_t ip) +{ + return parse_token_list(ip_list, ";", ip_match_wrapper, &ip); +} + +static bool port_match_wrapper(const char *token, const void *data) +{ + return match_port_pattern(token, *(const uint16_t*)data); +} + +static bool match_port_list(const char *port_list, uint16_t port) +{ + return parse_token_list(port_list, ",;", port_match_wrapper, &port); +} + +static bool match_process_pattern(const char *pattern, const char *process_full_path) +{ + if (pattern == NULL || strcmp(pattern, "*") == 0) + return true; + + const char *filename = strrchr(process_full_path, '/'); + if (filename != NULL) + filename++; + else + filename = process_full_path; + + size_t pattern_len = strlen(pattern); + size_t name_len = strlen(filename); + size_t full_path_len = strlen(process_full_path); + + bool is_full_path_pattern = (strchr(pattern, '/') != NULL); + const char *match_target = is_full_path_pattern ? process_full_path : filename; + size_t target_len = is_full_path_pattern ? full_path_len : name_len; + + if (pattern_len > 0 && pattern[pattern_len - 1] == '*') + { + return strncasecmp(pattern, match_target, pattern_len - 1) == 0; + } + + if (pattern_len > 1 && pattern[0] == '*') + { + const char *pattern_suffix = pattern + 1; + size_t suffix_len = pattern_len - 1; + if (target_len >= suffix_len) + { + return strcasecmp(match_target + target_len - suffix_len, pattern_suffix) == 0; + } + return false; + } + + return strcasecmp(pattern, match_target) == 0; +} + +typedef struct { + const char *process_name; +} process_match_data; + +static bool process_match_wrapper(const char *token, const void *data) +{ + const process_match_data *pdata = data; + return match_process_pattern(token, pdata->process_name); +} + +static bool match_process_list(const char *process_list, const char *process_name) +{ + process_match_data data = {process_name}; + return parse_token_list(process_list, ";", process_match_wrapper, &data); +} + +static uint32_t parse_ipv4(const char *ip) +{ + unsigned int a, b, c, d; + if (sscanf(ip, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) + return 0; + if (a > 255 || b > 255 || c > 255 || d > 255) + return 0; + return (a << 0) | (b << 8) | (c << 16) | (d << 24); +} + +static uint32_t resolve_hostname(const char *hostname) +{ + if (hostname == NULL || hostname[0] == '\0') + return 0; + + uint32_t ip = parse_ipv4(hostname); + if (ip != 0) + return ip; + + struct addrinfo hints, *result = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if (getaddrinfo(hostname, NULL, &hints, &result) != 0) + { + log_message("failed to resolve hostname: %s", hostname); + return 0; + } + + if (result == NULL || result->ai_family != AF_INET) + { + if (result != NULL) + freeaddrinfo(result); + log_message("no ipv4 address found for hostname: %s", hostname); + return 0; + } + + struct sockaddr_in *addr = (struct sockaddr_in *)result->ai_addr; + uint32_t resolved_ip = addr->sin_addr.s_addr; + freeaddrinfo(result); + + log_message("resolved %s to %d.%d.%d.%d", hostname, + (resolved_ip >> 0) & 0xFF, (resolved_ip >> 8) & 0xFF, + (resolved_ip >> 16) & 0xFF, (resolved_ip >> 24) & 0xFF); + + return resolved_ip; +} + +static bool is_broadcast_or_multicast(uint32_t ip) +{ + uint8_t first_octet = (ip >> 0) & 0xFF; + + if (ip == 0xFFFFFFFF) + return true; + + if (first_octet >= 224 && first_octet <= 239) + return true; + + return false; +} + +static RuleAction match_rule(const char *process_name, uint32_t dest_ip, uint16_t dest_port, bool is_udp) +{ + PROCESS_RULE *rule = rules_list; + PROCESS_RULE *wildcard_rule = NULL; + + while (rule != NULL) + { + if (!rule->enabled) + { + rule = rule->next; + continue; + } + + if (rule->protocol != RULE_PROTOCOL_BOTH) + { + if (rule->protocol == RULE_PROTOCOL_TCP && is_udp) + { + rule = rule->next; + continue; + } + if (rule->protocol == RULE_PROTOCOL_UDP && !is_udp) + { + rule = rule->next; + continue; + } + } + + bool is_wildcard_process = (strcmp(rule->process_name, "*") == 0 || strcasecmp(rule->process_name, "ANY") == 0); + + if (is_wildcard_process) + { + bool has_ip_filter = (strcmp(rule->target_hosts, "*") != 0); + bool has_port_filter = (strcmp(rule->target_ports, "*") != 0); + + if (has_ip_filter || has_port_filter) + { + if (match_ip_list(rule->target_hosts, dest_ip) && + match_port_list(rule->target_ports, dest_port)) + { + return rule->action; + } + rule = rule->next; + continue; + } + + if (wildcard_rule == NULL) + { + wildcard_rule = rule; + } + rule = rule->next; + continue; + } + + if (match_process_list(rule->process_name, process_name)) + { + if (match_ip_list(rule->target_hosts, dest_ip) && + match_port_list(rule->target_ports, dest_port)) + { + return rule->action; + } + } + + rule = rule->next; + } + + if (wildcard_rule != NULL) + { + return wildcard_rule->action; + } + + return RULE_ACTION_DIRECT; +} + +static RuleAction check_process_rule(uint32_t src_ip, uint16_t src_port, uint32_t dest_ip, uint16_t dest_port, bool is_udp, uint32_t *out_pid) +{ + uint32_t pid; + char process_name[MAX_PROCESS_NAME]; + + pid = get_process_id_from_connection(src_ip, src_port, is_udp); + + if (out_pid != NULL) + *out_pid = pid; + + if (pid == 0) + return RULE_ACTION_DIRECT; + + if (pid == g_current_process_id) + return RULE_ACTION_DIRECT; + + if (!get_process_name_from_pid(pid, process_name, sizeof(process_name))) + return RULE_ACTION_DIRECT; + + RuleAction action = match_rule(process_name, dest_ip, dest_port, is_udp); + + if (action == RULE_ACTION_PROXY && is_udp && g_proxy_type == PROXY_TYPE_HTTP) + { + return RULE_ACTION_DIRECT; + } + if (action == RULE_ACTION_PROXY && (g_proxy_host[0] == '\0' || g_proxy_port == 0)) + { + return RULE_ACTION_DIRECT; + } + + return action; +} + +static int socks5_connect(int s, uint32_t dest_ip, uint16_t dest_port) +{ + unsigned char buf[SOCKS5_BUFFER_SIZE]; + ssize_t len; + bool use_auth = (g_proxy_username[0] != '\0'); + + buf[0] = SOCKS5_VERSION; + if (use_auth) + { + buf[1] = 0x02; + buf[2] = SOCKS5_AUTH_NONE; + buf[3] = 0x02; + if (send(s, buf, 4, 0) != 4) + { + log_message("socks5 failed to send auth methods"); + return -1; + } + } + else + { + buf[1] = 0x01; + buf[2] = SOCKS5_AUTH_NONE; + if (send(s, buf, 3, 0) != 3) + { + log_message("socks5 failed to send auth methods"); + return -1; + } + } + + len = recv(s, buf, 2, 0); + if (len != 2 || buf[0] != SOCKS5_VERSION) + { + log_message("socks5 invalid auth response"); + return -1; + } + + if (buf[1] == 0x02 && use_auth) + { + size_t ulen = strlen(g_proxy_username); + size_t plen = strlen(g_proxy_password); + buf[0] = 0x01; + buf[1] = (unsigned char)ulen; + memcpy(buf + 2, g_proxy_username, ulen); + buf[2 + ulen] = (unsigned char)plen; + memcpy(buf + 3 + ulen, g_proxy_password, plen); + + if (send(s, buf, 3 + ulen + plen, 0) != (ssize_t)(3 + ulen + plen)) + { + log_message("socks5 failed to send credentials"); + return -1; + } + + len = recv(s, buf, 2, 0); + if (len != 2 || buf[0] != 0x01 || buf[1] != 0x00) + { + log_message("socks5 authentication failed"); + return -1; + } + } + else if (buf[1] != SOCKS5_AUTH_NONE) + { + log_message("socks5 unsupported auth method"); + return -1; + } + + buf[0] = SOCKS5_VERSION; + buf[1] = SOCKS5_CMD_CONNECT; + buf[2] = 0x00; + buf[3] = SOCKS5_ATYP_IPV4; + memcpy(buf + 4, &dest_ip, 4); + uint16_t port_net = htons(dest_port); + memcpy(buf + 8, &port_net, 2); + + if (send(s, buf, 10, 0) != 10) + { + log_message("socks5 failed to send connect request"); + return -1; + } + + len = recv(s, buf, 10, 0); + if (len < 10 || buf[0] != SOCKS5_VERSION || buf[1] != 0x00) + { + log_message("socks5 connect failed status %d", len > 1 ? buf[1] : -1); + return -1; + } + + return 0; +} + +static int http_connect(int s, uint32_t dest_ip, uint16_t dest_port) +{ + char buf[HTTP_BUFFER_SIZE]; + char dest_ip_str[32]; + format_ip_address(dest_ip, dest_ip_str, sizeof(dest_ip_str)); + + int len = snprintf(buf, sizeof(buf), + "CONNECT %s:%d HTTP/1.1\r\n" + "Host: %s:%d\r\n" + "Proxy-Connection: Keep-Alive\r\n", + dest_ip_str, dest_port, dest_ip_str, dest_port); + + if (g_proxy_username[0] != '\0') + { + char auth[512]; + snprintf(auth, sizeof(auth), "%s:%s", g_proxy_username, g_proxy_password); + + // simple base64 encode - production code should use proper base64 + len += snprintf(buf + len, sizeof(buf) - len, + "Proxy-Authorization: Basic %s\r\n", auth); + } + + len += snprintf(buf + len, sizeof(buf) - len, "\r\n"); + + if (send_all(s, buf, len) < 0) + { + log_message("http failed to send connect"); + return -1; + } + + ssize_t recv_len = recv(s, buf, sizeof(buf) - 1, 0); + if (recv_len < 12) + { + log_message("http invalid response"); + return -1; + } + + buf[recv_len] = '\0'; + if (strncmp(buf, "HTTP/1.", 7) != 0) + { + log_message("http invalid response"); + return -1; + } + + int status_code = 0; + if (sscanf(buf + 9, "%d", &status_code) != 1 || status_code != 200) + { + log_message("http connect failed status %d", status_code); + return -1; + } + + return 0; +} + +static void* transfer_handler(void *arg) +{ + TRANSFER_CONFIG *config = arg; + char buffer[65536]; + ssize_t n; + + while ((n = recv(config->from_socket, buffer, sizeof(buffer), 0)) > 0) + { + if (send_all(config->to_socket, buffer, n) < 0) + break; + } + + shutdown(config->from_socket, SHUT_RD); + shutdown(config->to_socket, SHUT_WR); + free(config); + return NULL; +} + +static void* connection_handler(void *arg) +{ + CONNECTION_CONFIG *config = arg; + int client_sock = config->client_socket; + uint32_t dest_ip = config->orig_dest_ip; + uint16_t dest_port = config->orig_dest_port; + int socks_sock; + struct sockaddr_in socks_addr; + uint32_t socks5_ip; + + free(config); + + socks5_ip = resolve_hostname(g_proxy_host); + if (socks5_ip == 0) + { + close(client_sock); + return NULL; + } + + socks_sock = socket(AF_INET, SOCK_STREAM, 0); + if (socks_sock < 0) + { + log_message("socket creation failed %d", errno); + close(client_sock); + return NULL; + } + + configure_tcp_socket(socks_sock, 524288, 30000); + configure_tcp_socket(client_sock, 524288, 30000); + + memset(&socks_addr, 0, sizeof(socks_addr)); + socks_addr.sin_family = AF_INET; + socks_addr.sin_addr.s_addr = socks5_ip; + socks_addr.sin_port = htons(g_proxy_port); + + if (connect(socks_sock, (struct sockaddr *)&socks_addr, sizeof(socks_addr)) < 0) + { + log_message("failed to connect to proxy %d", errno); + close(client_sock); + close(socks_sock); + return NULL; + } + + if (g_proxy_type == PROXY_TYPE_SOCKS5) + { + if (socks5_connect(socks_sock, dest_ip, dest_port) != 0) + { + close(client_sock); + close(socks_sock); + return NULL; + } + } + else if (g_proxy_type == PROXY_TYPE_HTTP) + { + if (http_connect(socks_sock, dest_ip, dest_port) != 0) + { + close(client_sock); + close(socks_sock); + return NULL; + } + } + + TRANSFER_CONFIG *client_to_socks = malloc(sizeof(TRANSFER_CONFIG)); + TRANSFER_CONFIG *socks_to_client = malloc(sizeof(TRANSFER_CONFIG)); + + if (!client_to_socks || !socks_to_client) + { + close(client_sock); + close(socks_sock); + free(client_to_socks); + free(socks_to_client); + return NULL; + } + + client_to_socks->from_socket = client_sock; + client_to_socks->to_socket = socks_sock; + socks_to_client->from_socket = socks_sock; + socks_to_client->to_socket = client_sock; + + pthread_t transfer_thread; + pthread_create(&transfer_thread, NULL, transfer_handler, client_to_socks); + pthread_detach(transfer_thread); + + transfer_handler(socks_to_client); + + close(client_sock); + close(socks_sock); + return NULL; +} + +static void* local_proxy_server(void *arg) +{ + (void)arg; + struct sockaddr_in addr; + int listen_sock; + int on = 1; + + listen_sock = socket(AF_INET, SOCK_STREAM, 0); + if (listen_sock < 0) + { + log_message("socket creation failed %d", errno); + return NULL; + } + + setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + int nodelay = 1; + setsockopt(listen_sock, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(g_local_relay_port); + + if (bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + log_message("bind failed %d", errno); + close(listen_sock); + return NULL; + } + + if (listen(listen_sock, SOMAXCONN) < 0) + { + log_message("listen failed %d", errno); + close(listen_sock); + return NULL; + } + + log_message("local proxy listening on port %d", g_local_relay_port); + + while (running) + { + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(listen_sock, &read_fds); + struct timeval timeout = {1, 0}; + + if (select(listen_sock + 1, &read_fds, NULL, NULL, &timeout) <= 0) + continue; + + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + int client_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &addr_len); + + if (client_sock < 0) + continue; + + log_message("[RELAY] accepted connection from %s:%u", + inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); + + CONNECTION_CONFIG *conn_config = malloc(sizeof(CONNECTION_CONFIG)); + if (conn_config == NULL) + { + close(client_sock); + continue; + } + + conn_config->client_socket = client_sock; + + uint16_t client_port = ntohs(client_addr.sin_port); + if (!get_connection(client_port, &conn_config->orig_dest_ip, &conn_config->orig_dest_port)) + { + log_message("[RELAY] no connection tracking for port %u", client_port); + close(client_sock); + free(conn_config); + continue; + } + + char dest_str[32]; + format_ip_address(conn_config->orig_dest_ip, dest_str, sizeof(dest_str)); + log_message("[RELAY] forwarding to %s:%u via proxy", dest_str, conn_config->orig_dest_port); + + pthread_t conn_thread; + if (pthread_create(&conn_thread, NULL, connection_handler, conn_config) != 0) + { + log_message("pthread_create failed %d", errno); + close(client_sock); + free(conn_config); + continue; + } + pthread_detach(conn_thread); + } + + close(listen_sock); + return NULL; +} + +// nfqueue packet callback +static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfad, void *data) +{ + (void)nfmsg; + (void)data; + + struct nfqnl_msg_packet_hdr *ph = nfq_get_msg_packet_hdr(nfad); + if (!ph) return nfq_set_verdict(qh, 0, NF_ACCEPT, 0, NULL); + + uint32_t id = ntohl(ph->packet_id); + + unsigned char *payload; + int payload_len = nfq_get_payload(nfad, &payload); + if (payload_len < (int)sizeof(struct iphdr)) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + + struct iphdr *iph = (struct iphdr *)payload; + + // fast path - no rules configured + if (!g_has_active_rules && g_connection_callback == NULL) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + + uint32_t src_ip = iph->saddr; + uint32_t dest_ip = iph->daddr; + uint16_t src_port = 0; + uint16_t dest_port = 0; + RuleAction action = RULE_ACTION_DIRECT; + uint32_t pid = 0; + + if (iph->protocol == IPPROTO_TCP) + { + if (payload_len < (int)(iph->ihl * 4 + sizeof(struct tcphdr))) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + + struct tcphdr *tcph = (struct tcphdr *)(payload + iph->ihl * 4); + src_port = ntohs(tcph->source); + dest_port = ntohs(tcph->dest); + + // skip packets from local relay + if (src_port == g_local_relay_port) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + + if (is_connection_tracked(src_port)) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + + // only process SYN packets for new connections + if (!(tcph->syn && !tcph->ack)) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + + if (dest_port == 53 && !g_dns_via_proxy) + action = RULE_ACTION_DIRECT; + else + action = check_process_rule(src_ip, src_port, dest_ip, dest_port, false, &pid); + + if (action == RULE_ACTION_PROXY && is_broadcast_or_multicast(dest_ip)) + action = RULE_ACTION_DIRECT; + + // log connection (skip our own process) + if (g_connection_callback != NULL && (tcph->syn && !tcph->ack) && pid > 0 && pid != g_current_process_id) + { + char process_name[MAX_PROCESS_NAME]; + if (get_process_name_from_pid(pid, process_name, sizeof(process_name))) + { + if (!is_connection_already_logged(pid, dest_ip, dest_port, action)) + { + char dest_ip_str[32]; + format_ip_address(dest_ip, dest_ip_str, sizeof(dest_ip_str)); + + char proxy_info[128]; + if (action == RULE_ACTION_PROXY) + { + snprintf(proxy_info, sizeof(proxy_info), "proxy %s://%s:%d", + g_proxy_type == PROXY_TYPE_HTTP ? "http" : "socks5", + g_proxy_host, g_proxy_port); + } + else if (action == RULE_ACTION_DIRECT) + { + snprintf(proxy_info, sizeof(proxy_info), "direct"); + } + else if (action == RULE_ACTION_BLOCK) + { + snprintf(proxy_info, sizeof(proxy_info), "blocked"); + } + + const char* display_name = extract_filename(process_name); + g_connection_callback(display_name, pid, dest_ip_str, dest_port, proxy_info); + + if (g_traffic_logging_enabled) + { + add_logged_connection(pid, dest_ip, dest_port, action); + } + } + } + } + + if (action == RULE_ACTION_DIRECT) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + else if (action == RULE_ACTION_BLOCK) + return nfq_set_verdict(qh, id, NF_DROP, 0, NULL); + else if (action == RULE_ACTION_PROXY) + { + // store connection info + add_connection(src_port, src_ip, dest_ip, dest_port); + + // mark packet so nat table REDIRECT rule will catch it + log_message("[REDIRECT] marking packet sport %u for redirect", src_port); + uint32_t mark = 1; + return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL); + } + } + else if (iph->protocol == IPPROTO_UDP) + { + if (payload_len < (int)(iph->ihl * 4 + sizeof(struct udphdr))) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + + struct udphdr *udph = (struct udphdr *)(payload + iph->ihl * 4); + src_port = ntohs(udph->source); + dest_port = ntohs(udph->dest); + + if (src_port == LOCAL_UDP_RELAY_PORT) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + + if (is_connection_tracked(src_port)) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + + if (dest_port == 53 && !g_dns_via_proxy) + action = RULE_ACTION_DIRECT; + else + action = check_process_rule(src_ip, src_port, dest_ip, dest_port, true, &pid); + + if (action == RULE_ACTION_PROXY && is_broadcast_or_multicast(dest_ip)) + action = RULE_ACTION_DIRECT; + + if (action == RULE_ACTION_PROXY && (dest_port == 67 || dest_port == 68)) + action = RULE_ACTION_DIRECT; + + // log (skip our own process) + if (g_connection_callback != NULL && pid > 0 && pid != g_current_process_id) + { + char process_name[MAX_PROCESS_NAME]; + if (get_process_name_from_pid(pid, process_name, sizeof(process_name))) + { + if (!is_connection_already_logged(pid, dest_ip, dest_port, action)) + { + char dest_ip_str[32]; + format_ip_address(dest_ip, dest_ip_str, sizeof(dest_ip_str)); + + char proxy_info[128]; + if (action == RULE_ACTION_PROXY) + { + snprintf(proxy_info, sizeof(proxy_info), "proxy socks5://%s:%d udp", + g_proxy_host, g_proxy_port); + } + else if (action == RULE_ACTION_DIRECT) + { + snprintf(proxy_info, sizeof(proxy_info), "direct udp"); + } + else if (action == RULE_ACTION_BLOCK) + { + snprintf(proxy_info, sizeof(proxy_info), "blocked udp"); + } + + const char* display_name = extract_filename(process_name); + g_connection_callback(display_name, pid, dest_ip_str, dest_port, proxy_info); + + if (g_traffic_logging_enabled) + { + add_logged_connection(pid, dest_ip, dest_port, action); + } + } + } + } + + if (action == RULE_ACTION_DIRECT) + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + else if (action == RULE_ACTION_BLOCK) + return nfq_set_verdict(qh, id, NF_DROP, 0, NULL); + else if (action == RULE_ACTION_PROXY) + { + add_connection(src_port, src_ip, dest_ip, dest_port); + + // add dynamic redirect rule for this source port + char cmd[256]; + snprintf(cmd, sizeof(cmd), + "iptables -t nat -A OUTPUT -p udp --sport %u -j REDIRECT --to-port %u 2>/dev/null", + src_port, LOCAL_UDP_RELAY_PORT); + system(cmd); + + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + } + } + + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); +} + +static void* packet_processor(void *arg) +{ + (void)arg; + char buf[4096] __attribute__((aligned)); + int fd = nfq_fd(nfq_h); + ssize_t rv; + + while (running) + { + rv = recv(fd, buf, sizeof(buf), 0); + if (rv >= 0) + nfq_handle_packet(nfq_h, buf, rv); + } + + return NULL; +} + +static inline uint32_t connection_hash(uint16_t port) +{ + return port % CONNECTION_HASH_SIZE; +} + +static void add_connection(uint16_t src_port, uint32_t src_ip, uint32_t dest_ip, uint16_t dest_port) +{ + uint32_t hash = connection_hash(src_port); + pthread_mutex_lock(&lock); + + CONNECTION_INFO *conn = connection_hash_table[hash]; + while (conn != NULL) + { + if (conn->src_port == src_port) + { + conn->orig_dest_ip = dest_ip; + conn->orig_dest_port = dest_port; + conn->src_ip = src_ip; + conn->is_tracked = true; + conn->last_activity = get_monotonic_ms(); + pthread_mutex_unlock(&lock); + return; + } + conn = conn->next; + } + + CONNECTION_INFO *new_conn = malloc(sizeof(CONNECTION_INFO)); + if (new_conn != NULL) + { + new_conn->src_port = src_port; + new_conn->src_ip = src_ip; + new_conn->orig_dest_ip = dest_ip; + new_conn->orig_dest_port = dest_port; + new_conn->is_tracked = true; + new_conn->last_activity = get_monotonic_ms(); + new_conn->next = connection_hash_table[hash]; + connection_hash_table[hash] = new_conn; + } + + pthread_mutex_unlock(&lock); +} + +static bool get_connection(uint16_t src_port, uint32_t *dest_ip, uint16_t *dest_port) +{ + uint32_t hash = connection_hash(src_port); + pthread_mutex_lock(&lock); + + CONNECTION_INFO *conn = connection_hash_table[hash]; + while (conn != NULL) + { + if (conn->src_port == src_port && conn->is_tracked) + { + *dest_ip = conn->orig_dest_ip; + *dest_port = conn->orig_dest_port; + conn->last_activity = get_monotonic_ms(); + pthread_mutex_unlock(&lock); + return true; + } + conn = conn->next; + } + + pthread_mutex_unlock(&lock); + return false; +} + +static bool is_connection_tracked(uint16_t src_port) +{ + uint32_t hash = connection_hash(src_port); + pthread_mutex_lock(&lock); + + CONNECTION_INFO *conn = connection_hash_table[hash]; + while (conn != NULL) + { + if (conn->src_port == src_port && conn->is_tracked) + { + pthread_mutex_unlock(&lock); + return true; + } + conn = conn->next; + } + + pthread_mutex_unlock(&lock); + return false; +} + +static void remove_connection(uint16_t src_port) +{ + uint32_t hash = connection_hash(src_port); + pthread_mutex_lock(&lock); + + CONNECTION_INFO **conn_ptr = &connection_hash_table[hash]; + while (*conn_ptr != NULL) + { + if ((*conn_ptr)->src_port == src_port) + { + CONNECTION_INFO *to_free = *conn_ptr; + *conn_ptr = (*conn_ptr)->next; + free(to_free); + pthread_mutex_unlock(&lock); + return; + } + conn_ptr = &(*conn_ptr)->next; + } + + pthread_mutex_unlock(&lock); +} + +static void cleanup_stale_connections(void) +{ + uint64_t current_time = get_monotonic_ms(); + pthread_mutex_lock(&lock); + + for (int i = 0; i < CONNECTION_HASH_SIZE; i++) + { + CONNECTION_INFO **conn_ptr = &connection_hash_table[i]; + while (*conn_ptr != NULL) + { + if (current_time - (*conn_ptr)->last_activity > 120000) // 120 sec timeout + { + CONNECTION_INFO *to_free = *conn_ptr; + *conn_ptr = (*conn_ptr)->next; + free(to_free); + } + else + { + conn_ptr = &(*conn_ptr)->next; + } + } + } + + pthread_mutex_unlock(&lock); +} + +static bool is_connection_already_logged(uint32_t pid, uint32_t dest_ip, uint16_t dest_port, RuleAction action) +{ + pthread_mutex_lock(&lock); + + LOGGED_CONNECTION *logged = logged_connections; + while (logged != NULL) + { + if (logged->pid == pid && logged->dest_ip == dest_ip && + logged->dest_port == dest_port && logged->action == action) + { + pthread_mutex_unlock(&lock); + return true; + } + logged = logged->next; + } + + pthread_mutex_unlock(&lock); + return false; +} + +static void add_logged_connection(uint32_t pid, uint32_t dest_ip, uint16_t dest_port, RuleAction action) +{ + pthread_mutex_lock(&lock); + + // keep only last 100 entries to avoid memory growth + int count = 0; + LOGGED_CONNECTION *temp = logged_connections; + while (temp != NULL && count < 100) + { + count++; + temp = temp->next; + } + + if (count >= 100) + { + temp = logged_connections; + for (int i = 0; i < 98 && temp != NULL; i++) + { + temp = temp->next; + } + + if (temp != NULL && temp->next != NULL) + { + LOGGED_CONNECTION *to_free_list = temp->next; + temp->next = NULL; + + pthread_mutex_unlock(&lock); + while (to_free_list != NULL) + { + LOGGED_CONNECTION *next = to_free_list->next; + free(to_free_list); + to_free_list = next; + } + pthread_mutex_lock(&lock); + } + } + + LOGGED_CONNECTION *logged = malloc(sizeof(LOGGED_CONNECTION)); + if (logged != NULL) + { + logged->pid = pid; + logged->dest_ip = dest_ip; + logged->dest_port = dest_port; + logged->action = action; + logged->next = logged_connections; + logged_connections = logged; + } + + pthread_mutex_unlock(&lock); +} + +static void clear_logged_connections(void) +{ + pthread_mutex_lock(&lock); + + while (logged_connections != NULL) + { + LOGGED_CONNECTION *to_free = logged_connections; + logged_connections = logged_connections->next; + free(to_free); + } + + pthread_mutex_unlock(&lock); +} + +static uint32_t pid_cache_hash(uint32_t src_ip, uint16_t src_port, bool is_udp) +{ + uint32_t hash = src_ip ^ ((uint32_t)src_port << 16) ^ (is_udp ? 0x80000000 : 0); + return hash % PID_CACHE_SIZE; +} + +static uint32_t get_cached_pid(uint32_t src_ip, uint16_t src_port, bool is_udp) +{ + uint32_t hash = pid_cache_hash(src_ip, src_port, is_udp); + uint64_t current_time = get_monotonic_ms(); + uint32_t pid = 0; + + pthread_mutex_lock(&lock); + + PID_CACHE_ENTRY *entry = pid_cache[hash]; + while (entry != NULL) + { + if (entry->src_ip == src_ip && + entry->src_port == src_port && + entry->is_udp == is_udp) + { + if (current_time - entry->timestamp < PID_CACHE_TTL_MS) + { + pid = entry->pid; + break; + } + else + { + break; + } + } + entry = entry->next; + } + + pthread_mutex_unlock(&lock); + return pid; +} + +static void cache_pid(uint32_t src_ip, uint16_t src_port, uint32_t pid, bool is_udp) +{ + uint32_t hash = pid_cache_hash(src_ip, src_port, is_udp); + uint64_t current_time = get_monotonic_ms(); + + pthread_mutex_lock(&lock); + + PID_CACHE_ENTRY *entry = pid_cache[hash]; + while (entry != NULL) + { + if (entry->src_ip == src_ip && + entry->src_port == src_port && + entry->is_udp == is_udp) + { + entry->pid = pid; + entry->timestamp = current_time; + pthread_mutex_unlock(&lock); + return; + } + entry = entry->next; + } + + PID_CACHE_ENTRY *new_entry = malloc(sizeof(PID_CACHE_ENTRY)); + if (new_entry != NULL) + { + new_entry->src_ip = src_ip; + new_entry->src_port = src_port; + new_entry->pid = pid; + new_entry->timestamp = current_time; + new_entry->is_udp = is_udp; + new_entry->next = pid_cache[hash]; + pid_cache[hash] = new_entry; + } + + pthread_mutex_unlock(&lock); +} + +static void clear_pid_cache(void) +{ + pthread_mutex_lock(&lock); + + for (int i = 0; i < PID_CACHE_SIZE; i++) + { + while (pid_cache[i] != NULL) + { + PID_CACHE_ENTRY *to_free = pid_cache[i]; + pid_cache[i] = pid_cache[i]->next; + free(to_free); + } + } + + pthread_mutex_unlock(&lock); +} + +static void* cleanup_worker(void *arg) +{ + (void)arg; + while (running) + { + sleep(30); + if (running) + { + cleanup_stale_connections(); + } + } + return NULL; +} + +static void update_has_active_rules(void) +{ + g_has_active_rules = false; + PROCESS_RULE *rule = rules_list; + while (rule != NULL) + { + if (rule->enabled) + { + g_has_active_rules = true; + break; + } + rule = rule->next; + } +} + +uint32_t ProxyBridge_AddRule(const char* process_name, const char* target_hosts, const char* target_ports, RuleProtocol protocol, RuleAction action) +{ + if (process_name == NULL || process_name[0] == '\0') + return 0; + + PROCESS_RULE *rule = malloc(sizeof(PROCESS_RULE)); + if (rule == NULL) + return 0; + + rule->rule_id = g_next_rule_id++; + strncpy(rule->process_name, process_name, MAX_PROCESS_NAME - 1); + rule->process_name[MAX_PROCESS_NAME - 1] = '\0'; + rule->protocol = protocol; + + if (target_hosts != NULL && target_hosts[0] != '\0') + { + rule->target_hosts = strdup(target_hosts); + if (rule->target_hosts == NULL) + { + free(rule); + return 0; + } + } + else + { + rule->target_hosts = strdup("*"); + if (rule->target_hosts == NULL) + { + free(rule); + return 0; + } + } + + if (target_ports != NULL && target_ports[0] != '\0') + { + rule->target_ports = strdup(target_ports); + if (rule->target_ports == NULL) + { + free(rule->target_hosts); + free(rule); + return 0; + } + } + else + { + rule->target_ports = strdup("*"); + if (rule->target_ports == NULL) + { + free(rule->target_hosts); + free(rule); + return 0; + } + } + + rule->action = action; + rule->enabled = true; + rule->next = rules_list; + rules_list = rule; + + update_has_active_rules(); + log_message("added rule id %u for process %s protocol %d action %d", rule->rule_id, process_name, protocol, action); + + return rule->rule_id; +} + +bool ProxyBridge_EnableRule(uint32_t rule_id) +{ + if (rule_id == 0) + return false; + + PROCESS_RULE *rule = rules_list; + while (rule != NULL) + { + if (rule->rule_id == rule_id) + { + rule->enabled = true; + update_has_active_rules(); + log_message("enabled rule id %u", rule_id); + return true; + } + rule = rule->next; + } + return false; +} + +bool ProxyBridge_DisableRule(uint32_t rule_id) +{ + if (rule_id == 0) + return false; + + PROCESS_RULE *rule = rules_list; + while (rule != NULL) + { + if (rule->rule_id == rule_id) + { + rule->enabled = false; + update_has_active_rules(); + log_message("disabled rule id %u", rule_id); + return true; + } + rule = rule->next; + } + return false; +} + +bool ProxyBridge_DeleteRule(uint32_t rule_id) +{ + if (rule_id == 0) + return false; + + PROCESS_RULE *rule = rules_list; + PROCESS_RULE *prev = NULL; + + while (rule != NULL) + { + if (rule->rule_id == rule_id) + { + if (prev == NULL) + rules_list = rule->next; + else + prev->next = rule->next; + + if (rule->target_hosts != NULL) + free(rule->target_hosts); + if (rule->target_ports != NULL) + free(rule->target_ports); + free(rule); + + update_has_active_rules(); + log_message("deleted rule id %u", rule_id); + return true; + } + prev = rule; + rule = rule->next; + } + return false; +} + +bool ProxyBridge_EditRule(uint32_t rule_id, const char* process_name, const char* target_hosts, const char* target_ports, RuleProtocol protocol, RuleAction action) +{ + if (rule_id == 0 || process_name == NULL || target_hosts == NULL || target_ports == NULL) + return false; + + PROCESS_RULE *rule = rules_list; + while (rule != NULL) + { + if (rule->rule_id == rule_id) + { + strncpy(rule->process_name, process_name, MAX_PROCESS_NAME - 1); + rule->process_name[MAX_PROCESS_NAME - 1] = '\0'; + + if (rule->target_hosts != NULL) + free(rule->target_hosts); + rule->target_hosts = strdup(target_hosts); + if (rule->target_hosts == NULL) + { + return false; + } + + if (rule->target_ports != NULL) + free(rule->target_ports); + rule->target_ports = strdup(target_ports); + if (rule->target_ports == NULL) + { + free(rule->target_hosts); + rule->target_hosts = NULL; + return false; + } + + rule->protocol = protocol; + rule->action = action; + + update_has_active_rules(); + log_message("updated rule id %u", rule_id); + return true; + } + rule = rule->next; + } + return false; +} + +bool ProxyBridge_SetProxyConfig(ProxyType type, const char* proxy_ip, uint16_t proxy_port, const char* username, const char* password) +{ + if (proxy_ip == NULL || proxy_ip[0] == '\0' || proxy_port == 0) + return false; + + if (resolve_hostname(proxy_ip) == 0) + return false; + + strncpy(g_proxy_host, proxy_ip, sizeof(g_proxy_host) - 1); + g_proxy_host[sizeof(g_proxy_host) - 1] = '\0'; + g_proxy_port = proxy_port; + g_proxy_type = (type == PROXY_TYPE_HTTP) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; + + if (username != NULL) + { + strncpy(g_proxy_username, username, sizeof(g_proxy_username) - 1); + g_proxy_username[sizeof(g_proxy_username) - 1] = '\0'; + } + else + { + g_proxy_username[0] = '\0'; + } + + if (password != NULL) + { + strncpy(g_proxy_password, password, sizeof(g_proxy_password) - 1); + g_proxy_password[sizeof(g_proxy_password) - 1] = '\0'; + } + else + { + g_proxy_password[0] = '\0'; + } + + log_message("proxy configured %s %s:%d", type == PROXY_TYPE_HTTP ? "http" : "socks5", proxy_ip, proxy_port); + return true; +} + +void ProxyBridge_SetDnsViaProxy(bool enable) +{ + g_dns_via_proxy = enable; + log_message("dns via proxy %s", enable ? "enabled" : "disabled"); +} + +void ProxyBridge_SetLogCallback(LogCallback callback) +{ + g_log_callback = callback; +} + +void ProxyBridge_SetConnectionCallback(ConnectionCallback callback) +{ + g_connection_callback = callback; +} + +void ProxyBridge_SetTrafficLoggingEnabled(bool enable) +{ + g_traffic_logging_enabled = enable; +} + +void ProxyBridge_ClearConnectionLogs(void) +{ + clear_logged_connections(); +} + +bool ProxyBridge_Start(void) +{ + if (running) + return false; + + running = true; + g_current_process_id = getpid(); + + if (pthread_create(&proxy_thread, NULL, local_proxy_server, NULL) != 0) + { + running = false; + return false; + } + + if (pthread_create(&cleanup_thread, NULL, cleanup_worker, NULL) != 0) + { + running = false; + pthread_cancel(proxy_thread); + return false; + } + + nfq_h = nfq_open(); + if (!nfq_h) + { + log_message("nfq_open failed"); + running = false; + return false; + } + + if (nfq_unbind_pf(nfq_h, AF_INET) < 0) + { + log_message("nfq_unbind_pf failed"); + } + + if (nfq_bind_pf(nfq_h, AF_INET) < 0) + { + log_message("nfq_bind_pf failed"); + nfq_close(nfq_h); + running = false; + return false; + } + + nfq_qh = nfq_create_queue(nfq_h, 0, &packet_callback, NULL); + if (!nfq_qh) + { + log_message("nfq_create_queue failed"); + nfq_close(nfq_h); + running = false; + return false; + } + + if (nfq_set_mode(nfq_qh, NFQNL_COPY_PACKET, 0xffff) < 0) + { + log_message("nfq_set_mode failed"); + nfq_destroy_queue(nfq_qh); + nfq_close(nfq_h); + running = false; + return false; + } + + // setup iptables rules for packet interception - USE MANGLE table so it runs BEFORE nat + log_message("setting up iptables rules"); + // mangle table runs before nat, so we can mark packets there + int ret1 = system("iptables -t mangle -A OUTPUT -p tcp -j NFQUEUE --queue-num 0"); + int ret2 = system("iptables -t mangle -A OUTPUT -p udp -j NFQUEUE --queue-num 0"); + + if (ret1 != 0 || ret2 != 0) { + log_message("failed to add iptables rules ret1=%d ret2=%d", ret1, ret2); + } else { + log_message("iptables nfqueue rules added successfully"); + } + + // setup nat redirect for marked packets + int ret3 = system("iptables -t nat -A OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010"); + if (ret3 != 0) { + log_message("failed to add nat redirect rule"); + } + + (void)ret3; + + for (int i = 0; i < NUM_PACKET_THREADS; i++) + { + if (pthread_create(&packet_thread[i], NULL, packet_processor, NULL) != 0) + { + log_message("failed to create packet thread %d", i); + } + } + + log_message("proxybridge started"); + return true; +} + +bool ProxyBridge_Stop(void) +{ + if (!running) + return false; + + running = false; + + // cleanup iptables + int ret1 = system("iptables -t mangle -D OUTPUT -p tcp -j NFQUEUE --queue-num 0 2>/dev/null"); + int ret2 = system("iptables -t mangle -D OUTPUT -p udp -j NFQUEUE --queue-num 0 2>/dev/null"); + int ret3 = system("iptables -t nat -D OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010 2>/dev/null"); + (void)ret1; + (void)ret2; + (void)ret3; + + for (int i = 0; i < NUM_PACKET_THREADS; i++) + { + if (packet_thread[i] != 0) + { + pthread_cancel(packet_thread[i]); + pthread_join(packet_thread[i], NULL); + packet_thread[i] = 0; + } + } + + if (nfq_qh) + { + nfq_destroy_queue(nfq_qh); + nfq_qh = NULL; + } + + if (nfq_h) + { + nfq_close(nfq_h); + nfq_h = NULL; + } + + if (proxy_thread != 0) + { + pthread_cancel(proxy_thread); + pthread_join(proxy_thread, NULL); + proxy_thread = 0; + } + + if (cleanup_thread != 0) + { + pthread_cancel(cleanup_thread); + pthread_join(cleanup_thread, NULL); + cleanup_thread = 0; + } + + clear_logged_connections(); + clear_pid_cache(); + + log_message("proxybridge stopped"); + return true; +} + +int ProxyBridge_TestConnection(const char* target_host, uint16_t target_port, char* result_buffer, size_t buffer_size) +{ + int test_sock = -1; + struct sockaddr_in proxy_addr; + struct hostent* host_info; + uint32_t target_ip; + int ret = -1; + char temp_buffer[512]; + + if (g_proxy_host[0] == '\0' || g_proxy_port == 0) + { + snprintf(result_buffer, buffer_size, "error no proxy configured"); + return -1; + } + + if (target_host == NULL || target_host[0] == '\0') + { + snprintf(result_buffer, buffer_size, "error invalid target host"); + return -1; + } + + snprintf(temp_buffer, sizeof(temp_buffer), "testing connection to %s:%d via %s proxy %s:%d\n", + target_host, target_port, + g_proxy_type == PROXY_TYPE_HTTP ? "http" : "socks5", + g_proxy_host, g_proxy_port); + strncpy(result_buffer, temp_buffer, buffer_size - 1); + result_buffer[buffer_size - 1] = '\0'; + + host_info = gethostbyname(target_host); + if (host_info == NULL) + { + snprintf(temp_buffer, sizeof(temp_buffer), "error failed to resolve hostname %s\n", target_host); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + return -1; + } + target_ip = *(uint32_t*)host_info->h_addr_list[0]; + + snprintf(temp_buffer, sizeof(temp_buffer), "resolved %s to %d.%d.%d.%d\n", + target_host, + (target_ip >> 0) & 0xFF, (target_ip >> 8) & 0xFF, + (target_ip >> 16) & 0xFF, (target_ip >> 24) & 0xFF); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + + test_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (test_sock < 0) + { + snprintf(temp_buffer, sizeof(temp_buffer), "error socket creation failed\n"); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + return -1; + } + + configure_tcp_socket(test_sock, 65536, 10000); + + memset(&proxy_addr, 0, sizeof(proxy_addr)); + proxy_addr.sin_family = AF_INET; + proxy_addr.sin_addr.s_addr = resolve_hostname(g_proxy_host); + proxy_addr.sin_port = htons(g_proxy_port); + + snprintf(temp_buffer, sizeof(temp_buffer), "connecting to proxy %s:%d\n", g_proxy_host, g_proxy_port); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + + if (connect(test_sock, (struct sockaddr*)&proxy_addr, sizeof(proxy_addr)) < 0) + { + snprintf(temp_buffer, sizeof(temp_buffer), "error failed to connect to proxy\n"); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + close(test_sock); + return -1; + } + + strncat(result_buffer, "connected to proxy server\n", buffer_size - strlen(result_buffer) - 1); + + if (g_proxy_type == PROXY_TYPE_SOCKS5) + { + if (socks5_connect(test_sock, target_ip, target_port) != 0) + { + snprintf(temp_buffer, sizeof(temp_buffer), "error socks5 handshake failed\n"); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + close(test_sock); + return -1; + } + strncat(result_buffer, "socks5 handshake successful\n", buffer_size - strlen(result_buffer) - 1); + } + else + { + if (http_connect(test_sock, target_ip, target_port) != 0) + { + snprintf(temp_buffer, sizeof(temp_buffer), "error http connect failed\n"); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + close(test_sock); + return -1; + } + strncat(result_buffer, "http connect successful\n", buffer_size - strlen(result_buffer) - 1); + } + + char http_request[512]; + snprintf(http_request, sizeof(http_request), + "GET / HTTP/1.1\r\n" + "Host: %s\r\n" + "Connection: close\r\n" + "User-Agent: ProxyBridge/1.0\r\n" + "\r\n", target_host); + + if (send_all(test_sock, http_request, strlen(http_request)) < 0) + { + snprintf(temp_buffer, sizeof(temp_buffer), "error failed to send test request\n"); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + close(test_sock); + return -1; + } + + strncat(result_buffer, "sent http get request\n", buffer_size - strlen(result_buffer) - 1); + char response[1024]; + ssize_t bytes_received = recv(test_sock, response, sizeof(response) - 1, 0); + if (bytes_received > 0) + { + response[bytes_received] = '\0'; + + if (strstr(response, "HTTP/") != NULL) + { + char* status_line = strstr(response, "HTTP/"); + int status_code = 0; + if (status_line != NULL) + { + sscanf(status_line, "HTTP/%*s %d", &status_code); + } + + snprintf(temp_buffer, sizeof(temp_buffer), "success received http %d response %ld bytes\n", status_code, (long)bytes_received); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + ret = 0; + } + else + { + snprintf(temp_buffer, sizeof(temp_buffer), "error received data but not valid http response\n"); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + ret = -1; + } + } + else + { + snprintf(temp_buffer, sizeof(temp_buffer), "error failed to receive response\n"); + strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); + ret = -1; + } + + close(test_sock); + + if (ret == 0) + { + strncat(result_buffer, "\nproxy connection test passed\n", buffer_size - strlen(result_buffer) - 1); + } + else + { + strncat(result_buffer, "\nproxy connection test failed\n", buffer_size - strlen(result_buffer) - 1); + } + + return ret; +} diff --git a/Linux/src/ProxyBridge.h b/Linux/src/ProxyBridge.h new file mode 100644 index 0000000..46e142d --- /dev/null +++ b/Linux/src/ProxyBridge.h @@ -0,0 +1,50 @@ +#ifndef PROXYBRIDGE_H +#define PROXYBRIDGE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*LogCallback)(const char* message); +typedef void (*ConnectionCallback)(const char* process_name, uint32_t pid, const char* dest_ip, uint16_t dest_port, const char* proxy_info); + +typedef enum { + PROXY_TYPE_HTTP = 0, + PROXY_TYPE_SOCKS5 = 1 +} ProxyType; + +typedef enum { + RULE_ACTION_PROXY = 0, + RULE_ACTION_DIRECT = 1, + RULE_ACTION_BLOCK = 2 +} RuleAction; + +typedef enum { + RULE_PROTOCOL_TCP = 0, + RULE_PROTOCOL_UDP = 1, + RULE_PROTOCOL_BOTH = 2 +} RuleProtocol; + +uint32_t ProxyBridge_AddRule(const char* process_name, const char* target_hosts, const char* target_ports, RuleProtocol protocol, RuleAction action); +bool ProxyBridge_EnableRule(uint32_t rule_id); +bool ProxyBridge_DisableRule(uint32_t rule_id); +bool ProxyBridge_DeleteRule(uint32_t rule_id); +bool ProxyBridge_EditRule(uint32_t rule_id, const char* process_name, const char* target_hosts, const char* target_ports, RuleProtocol protocol, RuleAction action); +bool ProxyBridge_SetProxyConfig(ProxyType type, const char* proxy_ip, uint16_t proxy_port, const char* username, const char* password); +void ProxyBridge_SetDnsViaProxy(bool enable); +void ProxyBridge_SetLogCallback(LogCallback callback); +void ProxyBridge_SetConnectionCallback(ConnectionCallback callback); +void ProxyBridge_SetTrafficLoggingEnabled(bool enable); +void ProxyBridge_ClearConnectionLogs(void); +bool ProxyBridge_Start(void); +bool ProxyBridge_Stop(void); +int ProxyBridge_TestConnection(const char* target_host, uint16_t target_port, char* result_buffer, size_t buffer_size); + +#ifdef __cplusplus +} +#endif + +#endif From d7a758a1a21c8138363df0b91e141abd3ec4f58f Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Tue, 3 Feb 2026 18:08:52 +0530 Subject: [PATCH 04/58] added udp support, added socsk5 assoicate, added caching, fixed connection track, fixed pid lookup, added falback, improved lookup speed moved windows core to Linux code --- .gitignore | 4 + Linux/cli/main.c | 24 ++- Linux/src/ProxyBridge.c | 458 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 460 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index bbf80e0..3a0bbe7 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,7 @@ Linux/output/** Linux/build/** MacOS/ProxyBridge/config/Signing-Config-ext.xcconfig MacOS/ProxyBridge/config/Signing-Config-app.xcconfig +Linux/cli/main.o +Linux/cli/proxybridge-cli +Linux/src/libproxybridge.so +Linux/src/ProxyBridge.o diff --git a/Linux/cli/main.c b/Linux/cli/main.c index c3c582b..2948ee0 100644 --- a/Linux/cli/main.c +++ b/Linux/cli/main.c @@ -107,14 +107,30 @@ int main(int argc, char *argv[]) ProxyBridge_SetDnsViaProxy(dns_via_proxy); ProxyBridge_SetTrafficLoggingEnabled(true); - // add rule - curl tcp traffic via proxy - uint32_t rule_id = ProxyBridge_AddRule("curl", "*", "*", RULE_PROTOCOL_TCP, RULE_ACTION_PROXY); - if (rule_id == 0) + // add rules - redirect specific apps to proxy + uint32_t rule_id1 = ProxyBridge_AddRule("curl", "*", "*", RULE_PROTOCOL_TCP, RULE_ACTION_PROXY); + if (rule_id1 == 0) { fprintf(stderr, "failed to add curl rule\n"); return 1; } - printf("added rule id %u: curl tcp traffic via proxy\n", rule_id); + printf("added rule id %u: curl tcp traffic via proxy\n", rule_id1); + + uint32_t rule_id2 = ProxyBridge_AddRule("nghttp", "*", "*", RULE_PROTOCOL_BOTH, RULE_ACTION_PROXY); + if (rule_id2 == 0) + { + fprintf(stderr, "failed to add nghttp rule\n"); + return 1; + } + printf("added rule id %u: nghttp tcp+udp traffic via proxy\n", rule_id2); + + uint32_t rule_id3 = ProxyBridge_AddRule("http3-client", "*", "*", RULE_PROTOCOL_UDP, RULE_ACTION_PROXY); + if (rule_id3 == 0) + { + fprintf(stderr, "failed to add http3-client rule\n"); + return 1; + } + printf("added rule id %u: http3-client udp traffic via proxy\n", rule_id3); // test connection char test_result[2048]; diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index d9a6b2f..94d4513 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -96,12 +96,21 @@ static struct nfq_handle *nfq_h = NULL; static struct nfq_q_handle *nfq_qh = NULL; static pthread_t packet_thread[NUM_PACKET_THREADS] = {0}; static pthread_t proxy_thread = 0; +static pthread_t udp_relay_thread = 0; static pthread_t cleanup_thread = 0; static PID_CACHE_ENTRY *pid_cache[PID_CACHE_SIZE] = {NULL}; static bool g_has_active_rules = false; static bool running = false; static uint32_t g_current_process_id = 0; +// UDP relay globals +static int udp_relay_socket = -1; +static int socks5_udp_control_socket = -1; +static int socks5_udp_send_socket = -1; +static struct sockaddr_in socks5_udp_relay_addr; +static bool udp_associate_connected = false; +static uint64_t last_udp_connect_attempt = 0; + static bool g_traffic_logging_enabled = true; static char g_proxy_host[256] = ""; @@ -270,7 +279,7 @@ static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_por req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.r.sdiag_family = AF_INET; req.r.sdiag_protocol = is_udp ? IPPROTO_UDP : IPPROTO_TCP; - req.r.idiag_states = is_udp ? (1 << TCP_ESTABLISHED) : -1; + req.r.idiag_states = -1; // All states (UDP is connectionless, doesn't have TCP states) req.r.idiag_ext = 0; struct sockaddr_nl sa; @@ -379,6 +388,76 @@ static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_por done: close(fd); + + // Fallback for UDP: scan /proc/net/udp if netlink didn't find the socket + // This happens when UDP socket uses sendto() without connect() + if (matches_found == 0 && is_udp) { + + // Try both IPv4 and IPv6 (socket might be IPv6 sending IPv4 packets) + const char* udp_files[] = {"/proc/net/udp", "/proc/net/udp6"}; + for (int file_idx = 0; file_idx < 2 && pid == 0; file_idx++) { + FILE *fp = fopen(udp_files[file_idx], "r"); + if (fp) { + char line[512]; + (void)fgets(line, sizeof(line), fp); // skip header + int entries_scanned = 0; + while (fgets(line, sizeof(line), fp)) { + unsigned int local_addr, local_port; + unsigned long inode; + int uid_val; + + // Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode + if (sscanf(line, "%*d: %X:%X %*X:%*X %*X %*X:%*X %*X:%*X %*X %d %*d %lu", + &local_addr, &local_port, &uid_val, &inode) == 4) { + entries_scanned++; + + if (local_port == src_port) { + target_inode = inode; + + // Now find PID from /proc/*/fd/* with this inode + DIR *proc_dir = opendir("/proc"); + if (proc_dir) { + struct dirent *proc_entry; + while ((proc_entry = readdir(proc_dir)) != NULL && pid == 0) { + if (proc_entry->d_type == DT_DIR && isdigit(proc_entry->d_name[0])) { + char fd_path[256]; + snprintf(fd_path, sizeof(fd_path), "/proc/%s/fd", proc_entry->d_name); + DIR *fd_dir = opendir(fd_path); + if (fd_dir) { + struct dirent *fd_entry; + while ((fd_entry = readdir(fd_dir)) != NULL && pid == 0) { + if (fd_entry->d_type == DT_LNK) { + char link_path[512]; + snprintf(link_path, sizeof(link_path), "/proc/%s/fd/%s", + proc_entry->d_name, fd_entry->d_name); + char link_target[256]; + ssize_t link_len = readlink(link_path, link_target, sizeof(link_target) - 1); + if (link_len > 0) { + link_target[link_len] = '\0'; + char expected[64]; + snprintf(expected, sizeof(expected), "socket:[%lu]", inode); + if (strcmp(link_target, expected) == 0) { + pid = (uint32_t)atoi(proc_entry->d_name); + break; + } + } + } + } + closedir(fd_dir); + } + } + } + closedir(proc_dir); + } + break; + } + } + } + fclose(fp); + } + } + } + if (pid != 0) cache_pid(src_ip, src_port, pid, is_udp); return pid; @@ -1056,6 +1135,304 @@ static void* local_proxy_server(void *arg) return NULL; } +// SOCKS5 UDP ASSOCIATE +static int socks5_udp_associate(int s, struct sockaddr_in *relay_addr) +{ + unsigned char buf[512]; + ssize_t len; + + // Auth handshake + bool use_auth = (g_proxy_username[0] != '\0'); + buf[0] = SOCKS5_VERSION; + buf[1] = use_auth ? 0x02 : 0x01; + buf[2] = SOCKS5_AUTH_NONE; + if (use_auth) + buf[3] = 0x02; // username/password auth + + if (send(s, buf, use_auth ? 4 : 3, 0) != (use_auth ? 4 : 3)) + return -1; + + len = recv(s, buf, 2, 0); + if (len != 2 || buf[0] != SOCKS5_VERSION) + return -1; + + if (buf[1] == 0x02 && use_auth) + { + size_t ulen = strlen(g_proxy_username); + size_t plen = strlen(g_proxy_password); + buf[0] = 0x01; + buf[1] = (unsigned char)ulen; + memcpy(buf + 2, g_proxy_username, ulen); + buf[2 + ulen] = (unsigned char)plen; + memcpy(buf + 3 + ulen, g_proxy_password, plen); + + if (send(s, buf, 3 + ulen + plen, 0) != (ssize_t)(3 + ulen + plen)) + return -1; + + len = recv(s, buf, 2, 0); + if (len != 2 || buf[0] != 0x01 || buf[1] != 0x00) + return -1; + } + else if (buf[1] != SOCKS5_AUTH_NONE) + return -1; + + // UDP ASSOCIATE request + buf[0] = SOCKS5_VERSION; + buf[1] = SOCKS5_CMD_UDP_ASSOCIATE; + buf[2] = 0x00; + buf[3] = SOCKS5_ATYP_IPV4; + memset(buf + 4, 0, 4); // 0.0.0.0 + memset(buf + 8, 0, 2); // port 0 + + if (send(s, buf, 10, 0) != 10) + return -1; + + len = recv(s, buf, 512, 0); + if (len < 10 || buf[0] != SOCKS5_VERSION || buf[1] != 0x00) + return -1; + + // Extract relay address + if (buf[3] == SOCKS5_ATYP_IPV4) + { + memset(relay_addr, 0, sizeof(*relay_addr)); + relay_addr->sin_family = AF_INET; + memcpy(&relay_addr->sin_addr.s_addr, buf + 4, 4); + memcpy(&relay_addr->sin_port, buf + 8, 2); + return 0; + } + + return -1; +} + +static bool establish_udp_associate(void) +{ + uint64_t now = get_monotonic_ms(); + if (now - last_udp_connect_attempt < 5000) + return false; + + last_udp_connect_attempt = now; + + if (socks5_udp_control_socket >= 0) + { + close(socks5_udp_control_socket); + socks5_udp_control_socket = -1; + } + if (socks5_udp_send_socket >= 0) + { + close(socks5_udp_send_socket); + socks5_udp_send_socket = -1; + } + + int tcp_sock = socket(AF_INET, SOCK_STREAM, 0); + if (tcp_sock < 0) + return false; + + configure_tcp_socket(tcp_sock, 262144, 3000); + + uint32_t socks5_ip = resolve_hostname(g_proxy_host); + if (socks5_ip == 0) + { + close(tcp_sock); + return false; + } + + struct sockaddr_in socks_addr; + memset(&socks_addr, 0, sizeof(socks_addr)); + socks_addr.sin_family = AF_INET; + socks_addr.sin_addr.s_addr = socks5_ip; + socks_addr.sin_port = htons(g_proxy_port); + + if (connect(tcp_sock, (struct sockaddr *)&socks_addr, sizeof(socks_addr)) < 0) + { + close(tcp_sock); + return false; + } + + if (socks5_udp_associate(tcp_sock, &socks5_udp_relay_addr) != 0) + { + close(tcp_sock); + return false; + } + + socks5_udp_control_socket = tcp_sock; + + socks5_udp_send_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (socks5_udp_send_socket < 0) + { + close(socks5_udp_control_socket); + socks5_udp_control_socket = -1; + return false; + } + + configure_udp_socket(socks5_udp_send_socket, 262144, 30000); + + udp_associate_connected = true; + log_message("[UDP] ASSOCIATE established with SOCKS5 proxy"); + return true; +} + +static void* udp_relay_server(void *arg) +{ + (void)arg; + struct sockaddr_in local_addr, from_addr; + unsigned char recv_buf[65536]; + unsigned char send_buf[65536]; + socklen_t from_len; + + udp_relay_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (udp_relay_socket < 0) + return NULL; + + int on = 1; + setsockopt(udp_relay_socket, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + configure_udp_socket(udp_relay_socket, 262144, 30000); + + memset(&local_addr, 0, sizeof(local_addr)); + local_addr.sin_family = AF_INET; + local_addr.sin_addr.s_addr = INADDR_ANY; + local_addr.sin_port = htons(LOCAL_UDP_RELAY_PORT); + + if (bind(udp_relay_socket, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) + { + close(udp_relay_socket); + udp_relay_socket = -1; + return NULL; + } + + udp_associate_connected = establish_udp_associate(); + + log_message("[UDP] relay listening on port %d", LOCAL_UDP_RELAY_PORT); + if (!udp_associate_connected) + log_message("[UDP] ASSOCIATE not available yet - will retry when needed"); + + while (running) + { + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(udp_relay_socket, &read_fds); + + int max_fd = udp_relay_socket; + + if (udp_associate_connected && socks5_udp_send_socket >= 0) + { + FD_SET(socks5_udp_send_socket, &read_fds); + if (socks5_udp_send_socket > max_fd) + max_fd = socks5_udp_send_socket; + } + + struct timeval timeout = {1, 0}; + + if (select(max_fd + 1, &read_fds, NULL, NULL, &timeout) <= 0) + continue; + + // Client -> SOCKS5 proxy + if (FD_ISSET(udp_relay_socket, &read_fds)) + { + from_len = sizeof(from_addr); + ssize_t recv_len = recvfrom(udp_relay_socket, recv_buf, sizeof(recv_buf), 0, + (struct sockaddr *)&from_addr, &from_len); + if (recv_len <= 0) + continue; + + if (!udp_associate_connected) + { + if (!establish_udp_associate()) + continue; + } + + uint16_t client_port = ntohs(from_addr.sin_port); + uint32_t dest_ip; + uint16_t dest_port; + + if (!get_connection(client_port, &dest_ip, &dest_port)) + continue; + + // Build SOCKS5 UDP packet: +----+------+------+----------+----------+----------+ + // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | + // +----+------+------+----------+----------+----------+ + send_buf[0] = 0x00; // RSV + send_buf[1] = 0x00; // RSV + send_buf[2] = 0x00; // FRAG + send_buf[3] = SOCKS5_ATYP_IPV4; + memcpy(send_buf + 4, &dest_ip, 4); + uint16_t port_net = htons(dest_port); + memcpy(send_buf + 8, &port_net, 2); + memcpy(send_buf + 10, recv_buf, recv_len); + + sendto(socks5_udp_send_socket, send_buf, 10 + recv_len, 0, + (struct sockaddr *)&socks5_udp_relay_addr, sizeof(socks5_udp_relay_addr)); + } + + // SOCKS5 proxy -> Client + if (udp_associate_connected && socks5_udp_send_socket >= 0 && FD_ISSET(socks5_udp_send_socket, &read_fds)) + { + from_len = sizeof(from_addr); + ssize_t recv_len = recvfrom(socks5_udp_send_socket, recv_buf, sizeof(recv_buf), 0, + (struct sockaddr *)&from_addr, &from_len); + if (recv_len < 10) + continue; + + // Parse SOCKS5 UDP packet + if (recv_buf[3] != SOCKS5_ATYP_IPV4) + continue; + + uint32_t src_ip; + uint16_t src_port; + memcpy(&src_ip, recv_buf + 4, 4); + memcpy(&src_port, recv_buf + 8, 2); + src_port = ntohs(src_port); + + // Find the client that sent a packet to this destination + // Iterate through hash table to find matching dest_ip:dest_port + pthread_mutex_lock(&lock); + + struct sockaddr_in client_addr; + bool found_client = false; + + for (int hash = 0; hash < CONNECTION_HASH_SIZE; hash++) + { + CONNECTION_INFO *conn = connection_hash_table[hash]; + while (conn != NULL) + { + if (conn->orig_dest_ip == src_ip && + conn->orig_dest_port == src_port) + { + // Found the connection - send response back to original client port + memset(&client_addr, 0, sizeof(client_addr)); + client_addr.sin_family = AF_INET; + client_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + client_addr.sin_port = htons(conn->src_port); + found_client = true; + break; + } + conn = conn->next; + } + if (found_client) + break; + } + + pthread_mutex_unlock(&lock); + + if (found_client) + { + // Send unwrapped UDP data back to client + ssize_t data_len = recv_len - 10; + sendto(udp_relay_socket, recv_buf + 10, data_len, 0, + (struct sockaddr *)&client_addr, sizeof(client_addr)); + } + } + } + + if (socks5_udp_control_socket >= 0) + close(socks5_udp_control_socket); + if (socks5_udp_send_socket >= 0) + close(socks5_udp_send_socket); + if (udp_relay_socket >= 0) + close(udp_relay_socket); + + return NULL; +} + // nfqueue packet callback static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfad, void *data) { @@ -1127,17 +1504,17 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru char proxy_info[128]; if (action == RULE_ACTION_PROXY) { - snprintf(proxy_info, sizeof(proxy_info), "proxy %s://%s:%d", + snprintf(proxy_info, sizeof(proxy_info), "proxy %s://%s:%d tcp", g_proxy_type == PROXY_TYPE_HTTP ? "http" : "socks5", g_proxy_host, g_proxy_port); } else if (action == RULE_ACTION_DIRECT) { - snprintf(proxy_info, sizeof(proxy_info), "direct"); + snprintf(proxy_info, sizeof(proxy_info), "direct tcp"); } else if (action == RULE_ACTION_BLOCK) { - snprintf(proxy_info, sizeof(proxy_info), "blocked"); + snprintf(proxy_info, sizeof(proxy_info), "blocked tcp"); } const char* display_name = extract_filename(process_name); @@ -1191,15 +1568,37 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru if (action == RULE_ACTION_PROXY && (dest_port == 67 || dest_port == 68)) action = RULE_ACTION_DIRECT; + + // UDP proxy only works with SOCKS5, not HTTP + if (action == RULE_ACTION_PROXY && g_proxy_type != PROXY_TYPE_SOCKS5) + action = RULE_ACTION_DIRECT; + + // UDP proxy only works with SOCKS5, not HTTP + if (action == RULE_ACTION_PROXY && g_proxy_type != PROXY_TYPE_SOCKS5) + action = RULE_ACTION_DIRECT; + + // UDP proxy only works with SOCKS5, not HTTP + if (action == RULE_ACTION_PROXY && g_proxy_type != PROXY_TYPE_SOCKS5) + action = RULE_ACTION_DIRECT; - // log (skip our own process) - if (g_connection_callback != NULL && pid > 0 && pid != g_current_process_id) + // log (skip our own process, log even without PID for ephemeral UDP sockets) + if (g_connection_callback != NULL && pid != g_current_process_id) { char process_name[MAX_PROCESS_NAME]; - if (get_process_name_from_pid(pid, process_name, sizeof(process_name))) + uint32_t log_pid = (pid == 0) ? 1 : pid; // Use PID 1 for unknown processes + + if (pid > 0 && get_process_name_from_pid(pid, process_name, sizeof(process_name))) + { + // Got process name from PID + } + else + { + // UDP socket not found - ephemeral or timing issue + snprintf(process_name, sizeof(process_name), "unknown"); + } + + if (!is_connection_already_logged(log_pid, dest_ip, dest_port, action)) { - if (!is_connection_already_logged(pid, dest_ip, dest_port, action)) - { char dest_ip_str[32]; format_ip_address(dest_ip, dest_ip_str, sizeof(dest_ip_str)); @@ -1219,14 +1618,13 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru } const char* display_name = extract_filename(process_name); - g_connection_callback(display_name, pid, dest_ip_str, dest_port, proxy_info); + g_connection_callback(display_name, log_pid, dest_ip_str, dest_port, proxy_info); if (g_traffic_logging_enabled) { - add_logged_connection(pid, dest_ip, dest_port, action); + add_logged_connection(log_pid, dest_ip, dest_port, action); } } - } } if (action == RULE_ACTION_DIRECT) @@ -1235,16 +1633,12 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru return nfq_set_verdict(qh, id, NF_DROP, 0, NULL); else if (action == RULE_ACTION_PROXY) { + // UDP proxy via SOCKS5 UDP ASSOCIATE add_connection(src_port, src_ip, dest_ip, dest_port); - // add dynamic redirect rule for this source port - char cmd[256]; - snprintf(cmd, sizeof(cmd), - "iptables -t nat -A OUTPUT -p udp --sport %u -j REDIRECT --to-port %u 2>/dev/null", - src_port, LOCAL_UDP_RELAY_PORT); - system(cmd); - - return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); + // Mark UDP packet for redirect to local UDP relay (port 34011) + uint32_t mark = 2; // Use mark=2 for UDP (mark=1 is for TCP) + return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL); } } @@ -1872,6 +2266,15 @@ bool ProxyBridge_Start(void) return false; } + // Start UDP relay server if SOCKS5 proxy + if (g_proxy_type == PROXY_TYPE_SOCKS5) + { + if (pthread_create(&udp_relay_thread, NULL, udp_relay_server, NULL) != 0) + { + log_message("failed to create UDP relay thread"); + } + } + nfq_h = nfq_open(); if (!nfq_h) { @@ -1925,11 +2328,13 @@ bool ProxyBridge_Start(void) // setup nat redirect for marked packets int ret3 = system("iptables -t nat -A OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010"); - if (ret3 != 0) { - log_message("failed to add nat redirect rule"); + int ret4 = system("iptables -t nat -A OUTPUT -p udp -m mark --mark 2 -j REDIRECT --to-port 34011"); + if (ret3 != 0 || ret4 != 0) { + log_message("failed to add nat redirect rules"); } (void)ret3; + (void)ret4; for (int i = 0; i < NUM_PACKET_THREADS; i++) { @@ -1954,9 +2359,11 @@ bool ProxyBridge_Stop(void) int ret1 = system("iptables -t mangle -D OUTPUT -p tcp -j NFQUEUE --queue-num 0 2>/dev/null"); int ret2 = system("iptables -t mangle -D OUTPUT -p udp -j NFQUEUE --queue-num 0 2>/dev/null"); int ret3 = system("iptables -t nat -D OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010 2>/dev/null"); + int ret4 = system("iptables -t nat -D OUTPUT -p udp -m mark --mark 2 -j REDIRECT --to-port 34011 2>/dev/null"); (void)ret1; (void)ret2; (void)ret3; + (void)ret4; for (int i = 0; i < NUM_PACKET_THREADS; i++) { @@ -1987,6 +2394,13 @@ bool ProxyBridge_Stop(void) proxy_thread = 0; } + if (udp_relay_thread != 0) + { + pthread_cancel(udp_relay_thread); + pthread_join(udp_relay_thread, NULL); + udp_relay_thread = 0; + } + if (cleanup_thread != 0) { pthread_cancel(cleanup_thread); From 563a99940698901e13c7eb5035280960f05873a1 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Tue, 3 Feb 2026 18:17:40 +0530 Subject: [PATCH 05/58] added ip filter hardcode , resved pid check moved windows filter validation for broadcast ip and pid reserved to linux --- Linux/src/ProxyBridge.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index 94d4513..ebbc06e 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -468,6 +468,14 @@ static bool get_process_name_from_pid(uint32_t pid, char *name, size_t name_size if (pid == 0) return false; + // PID 1 is reserved for init/systemd on Linux + // Similar to Windows PID 4 (System), some system processes use PID 1 + if (pid == 1) + { + snprintf(name, name_size, "systemd"); + return true; + } + char path[64]; snprintf(path, sizeof(path), "/proc/%u/exe", pid); @@ -670,11 +678,25 @@ static uint32_t resolve_hostname(const char *hostname) static bool is_broadcast_or_multicast(uint32_t ip) { + // Localhost: 127.0.0.0/8 (127.x.x.x) uint8_t first_octet = (ip >> 0) & 0xFF; - + if (first_octet == 127) + return true; + + // APIPA (Link-Local): 169.254.0.0/16 (169.254.x.x) + uint8_t second_octet = (ip >> 8) & 0xFF; + if (first_octet == 169 && second_octet == 254) + return true; + + // Broadcast: 255.255.255.255 if (ip == 0xFFFFFFFF) return true; - + + // x.x.x.255 (subnet broadcasts) + if ((ip & 0xFF000000) == 0xFF000000) + return true; + + // Multicast: 224.0.0.0 - 239.255.255.255 (first octet 224-239) if (first_octet >= 224 && first_octet <= 239) return true; From 1d968b3c3ce5782c9c8bec2f914ddf3ab57ac582 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Tue, 3 Feb 2026 18:44:44 +0530 Subject: [PATCH 06/58] improved performance, impvord memory size, fixed allocation, added cleanup. improved compilation --- Linux/cli/Makefile | 14 ++++- Linux/src/Makefile | 14 ++++- Linux/src/ProxyBridge.c | 112 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 131 insertions(+), 9 deletions(-) diff --git a/Linux/cli/Makefile b/Linux/cli/Makefile index 083a14b..d392069 100644 --- a/Linux/cli/Makefile +++ b/Linux/cli/Makefile @@ -1,6 +1,14 @@ CC = gcc -CFLAGS = -Wall -Wextra -O2 -I../src -LDFLAGS = -L../src +CFLAGS = -Wall -Wextra -O3 -I../src \ + -fstack-protector-strong \ + -D_FORTIFY_SOURCE=2 \ + -fPIE \ + -Wformat -Wformat-security \ + -Werror=format-security \ + -fno-strict-overflow \ + -fno-delete-null-pointer-checks \ + -fwrapv +LDFLAGS = -L../src -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s LIBS = -lproxybridge -lpthread TARGET = proxybridge-cli @@ -11,6 +19,8 @@ all: $(TARGET) $(TARGET): $(OBJECTS) $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) + @echo "Stripping debug symbols..." + strip --strip-all $@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ diff --git a/Linux/src/Makefile b/Linux/src/Makefile index d134594..29effb0 100644 --- a/Linux/src/Makefile +++ b/Linux/src/Makefile @@ -1,6 +1,14 @@ CC = gcc -CFLAGS = -Wall -Wextra -O2 -fPIC -D_GNU_SOURCE -LDFLAGS = -shared +CFLAGS = -Wall -Wextra -O3 -fPIC -D_GNU_SOURCE \ + -fstack-protector-strong \ + -D_FORTIFY_SOURCE=2 \ + -fPIE \ + -Wformat -Wformat-security \ + -Werror=format-security \ + -fno-strict-overflow \ + -fno-delete-null-pointer-checks \ + -fwrapv +LDFLAGS = -shared -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s LIBS = -lpthread -lnetfilter_queue -lnfnetlink TARGET = libproxybridge.so @@ -11,6 +19,8 @@ all: $(TARGET) $(TARGET): $(OBJECTS) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + @echo "Stripping debug symbols..." + strip --strip-unneeded $@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index ebbc06e..9f981a4 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -1793,27 +1793,89 @@ static void remove_connection(uint16_t src_port) static void cleanup_stale_connections(void) { - uint64_t current_time = get_monotonic_ms(); - pthread_mutex_lock(&lock); + uint64_t now = get_monotonic_ms(); + int removed = 0; + // Process each hash bucket with minimal lock time for (int i = 0; i < CONNECTION_HASH_SIZE; i++) { + pthread_mutex_lock(&lock); CONNECTION_INFO **conn_ptr = &connection_hash_table[i]; + while (*conn_ptr != NULL) { - if (current_time - (*conn_ptr)->last_activity > 120000) // 120 sec timeout + if (now - (*conn_ptr)->last_activity > 60000) // 60 sec timeout (same as Windows) { CONNECTION_INFO *to_free = *conn_ptr; *conn_ptr = (*conn_ptr)->next; - free(to_free); + pthread_mutex_unlock(&lock); + free(to_free); // Free outside lock + removed++; + pthread_mutex_lock(&lock); } else { conn_ptr = &(*conn_ptr)->next; } } + pthread_mutex_unlock(&lock); + } + + // Cleanup PID cache + uint64_t now_cache = get_monotonic_ms(); + int cache_removed = 0; + for (int i = 0; i < PID_CACHE_SIZE; i++) + { + pthread_mutex_lock(&lock); + PID_CACHE_ENTRY **entry_ptr = &pid_cache[i]; + while (*entry_ptr != NULL) + { + if (now_cache - (*entry_ptr)->timestamp > 10000) // 10 sec cache TTL + { + PID_CACHE_ENTRY *to_free = *entry_ptr; + *entry_ptr = (*entry_ptr)->next; + pthread_mutex_unlock(&lock); + free(to_free); // Free outside lock + cache_removed++; + pthread_mutex_lock(&lock); + } + else + { + entry_ptr = &(*entry_ptr)->next; + } + } + pthread_mutex_unlock(&lock); + } + + // Keep only last 100 logged connections for memory efficiency + pthread_mutex_lock(&lock); + int logged_count = 0; + LOGGED_CONNECTION *temp = logged_connections; + while (temp != NULL) + { + logged_count++; + temp = temp->next; } + if (logged_count > 100) + { + temp = logged_connections; + for (int i = 0; i < 99 && temp != NULL; i++) + { + temp = temp->next; + } + if (temp != NULL && temp->next != NULL) + { + LOGGED_CONNECTION *to_free = temp->next; + temp->next = NULL; + while (to_free != NULL) + { + LOGGED_CONNECTION *next = to_free->next; + free(to_free); + to_free = next; + } + } + } pthread_mutex_unlock(&lock); } @@ -1999,7 +2061,7 @@ static void* cleanup_worker(void *arg) (void)arg; while (running) { - sleep(30); + sleep(30); // 30 seconds if (running) { cleanup_stale_connections(); @@ -2333,9 +2395,16 @@ bool ProxyBridge_Start(void) nfq_destroy_queue(nfq_qh); nfq_close(nfq_h); running = false; + pthread_cancel(cleanup_thread); + pthread_cancel(proxy_thread); + if (udp_relay_thread != 0) + pthread_cancel(udp_relay_thread); return false; } + // Set larger queue length for better performance (16384 like Windows) + nfq_set_queue_maxlen(nfq_qh, 16384); + // setup iptables rules for packet interception - USE MANGLE table so it runs BEFORE nat log_message("setting up iptables rules"); // mangle table runs before nat, so we can mark packets there @@ -2430,6 +2499,19 @@ bool ProxyBridge_Stop(void) cleanup_thread = 0; } + // Free all connections in hash table + pthread_mutex_lock(&lock); + for (int i = 0; i < CONNECTION_HASH_SIZE; i++) + { + while (connection_hash_table[i] != NULL) + { + CONNECTION_INFO *to_free = connection_hash_table[i]; + connection_hash_table[i] = connection_hash_table[i]->next; + free(to_free); + } + } + pthread_mutex_unlock(&lock); + clear_logged_connections(); clear_pid_cache(); @@ -2594,3 +2676,23 @@ int ProxyBridge_TestConnection(const char* target_host, uint16_t target_port, ch return ret; } + +// Library destructor - automatically cleanup when library is unloaded +__attribute__((destructor)) +static void library_cleanup(void) +{ + if (running) + { + log_message("library unloading - cleaning up automatically"); + ProxyBridge_Stop(); + } + else + { + // Even if not running, ensure iptables rules are removed + // This handles cases where the app crashed before calling Stop + system("iptables -t mangle -D OUTPUT -p tcp -j NFQUEUE --queue-num 0 2>/dev/null"); + system("iptables -t mangle -D OUTPUT -p udp -j NFQUEUE --queue-num 0 2>/dev/null"); + system("iptables -t nat -D OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010 2>/dev/null"); + system("iptables -t nat -D OUTPUT -p udp -m mark --mark 2 -j REDIRECT --to-port 34011 2>/dev/null"); + } +} From d702722cc5d1a2f1aeb7562c57e966d7f765f67e Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Wed, 4 Feb 2026 01:45:17 +0530 Subject: [PATCH 07/58] Fixed app crash, too much memroy and threads usage on connection added build scrippt, Copied Windows CLI into Linux C CLI --- Linux/build.sh | 71 ++++++ Linux/cli/main.c | 507 +++++++++++++++++++++++++++++++--------- Linux/src/ProxyBridge.c | 190 +++++++++------ 3 files changed, 585 insertions(+), 183 deletions(-) create mode 100755 Linux/build.sh diff --git a/Linux/build.sh b/Linux/build.sh new file mode 100755 index 0000000..95faec7 --- /dev/null +++ b/Linux/build.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +set -e # Exit on error + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OUTPUT_DIR="$SCRIPT_DIR/output" + +# Remove and recreate output directory +if [ -d "$OUTPUT_DIR" ]; then + echo "Removing existing output directory..." + rm -rf "$OUTPUT_DIR" +fi + +echo "Creating output directory..." +mkdir -p "$OUTPUT_DIR" +echo "" + +# Build library +echo "=== Building Library ===" +cd "$SCRIPT_DIR/src" +make clean 2>/dev/null || true +make + +if [ -f "libproxybridge.so" ]; then + echo "✓ Library build successful" +else + echo "✗ Library build failed!" + exit 1 +fi +echo "" + +# Build CLI (library must stay for linking) +echo "=== Building CLI ===" +cd "$SCRIPT_DIR/cli" +make clean 2>/dev/null || true +make + +if [ -f "proxybridge-cli" ]; then + echo "✓ CLI build successful" +else + echo "✗ CLI build failed!" + exit 1 +fi +echo "" + +# Move binaries to output +echo "Moving binaries to output directory..." +mv "$SCRIPT_DIR/src/libproxybridge.so" "$OUTPUT_DIR/" +mv proxybridge-cli "$OUTPUT_DIR/" +echo "✓ Binaries moved to output" +echo "" + +# Cleanup build files +echo "Cleaning up build artifacts..." +cd "$SCRIPT_DIR/src" +rm -f *.o +make clean 2>/dev/null || true +cd "$SCRIPT_DIR/cli" +rm -f *.o +make clean 2>/dev/null || true +echo "✓ Cleanup complete" +echo "" + +# Show results +echo "===================================" +echo "Build Complete!" +echo "===================================" +cd "$OUTPUT_DIR" +ls -lh +echo "" +echo "Output location: $OUTPUT_DIR" diff --git a/Linux/cli/main.c b/Linux/cli/main.c index 2948ee0..eee7b8d 100644 --- a/Linux/cli/main.c +++ b/Linux/cli/main.c @@ -4,177 +4,466 @@ #include #include #include +#include #include "../src/ProxyBridge.h" -static volatile bool keep_running = true; +#define MAX_RULES 100 +#define MAX_RULE_STR 512 + +typedef struct { + char process_name[256]; + char target_hosts[256]; + char target_ports[256]; + RuleProtocol protocol; + RuleAction action; +} ProxyRule; + +static volatile bool keep_running = false; +static int verbose_level = 0; static void log_callback(const char* message) { - printf("[LOG] %s\n", message); + if (verbose_level == 1 || verbose_level == 3) + { + printf("[LOG] %s\n", message); + } } static void connection_callback(const char* process_name, uint32_t pid, const char* dest_ip, uint16_t dest_port, const char* proxy_info) { - printf("[CONN] %s (PID: %u) -> %s:%u via %s\n", - process_name, pid, dest_ip, dest_port, proxy_info); + if (verbose_level == 2 || verbose_level == 3) + { + printf("[CONN] %s (PID:%u) -> %s:%u via %s\n", + process_name, pid, dest_ip, dest_port, proxy_info); + } } static void signal_handler(int sig) { - (void)sig; - keep_running = false; + if (sig == SIGSEGV || sig == SIGABRT || sig == SIGBUS) + { + printf("\n\n=== CLI CRASH DETECTED ===\n"); + printf("Signal: %d (%s)\n", sig, + sig == SIGSEGV ? "SEGFAULT" : + sig == SIGABRT ? "ABORT" : "BUS ERROR"); + printf("Calling emergency cleanup...\n"); + ProxyBridge_Stop(); + _exit(1); + } + + if (keep_running) + { + printf("\n\nStopping ProxyBridge...\n"); + keep_running = false; + } +} + +static void show_banner(void) +{ + printf("\n"); + printf(" ____ ____ _ _ \n"); + printf(" | _ \\ _ __ _____ ___ _ | __ ) _ __(_) __| | __ _ ___ \n"); + printf(" | |_) | '__/ _ \\ \\/ / | | | | _ \\| '__| |/ _` |/ _` |/ _ \\\n"); + printf(" | __/| | | (_) > <| |_| | | |_) | | | | (_| | (_| | __/\n"); + printf(" |_| |_| \\___/_/\\_\\\\__, | |____/|_| |_|\\__,_|\\__, |\\___|\n"); + printf(" |___/ |___/ V3.1.0\n"); + printf("\n"); + printf(" Universal proxy client for Linux applications\n"); + printf("\n"); + printf("\tAuthor: Sourav Kalal/InterceptSuite\n"); + printf("\tGitHub: https://github.com/InterceptSuite/ProxyBridge\n"); + printf("\n"); +} + +static void show_help(const char* prog) +{ + show_banner(); + printf("USAGE:\n"); + printf(" %s [OPTIONS]\n\n", prog); + + printf("OPTIONS:\n"); + printf(" --proxy Proxy server URL with optional authentication\n"); + printf(" Format: type://ip:port or type://ip:port:username:password\n"); + printf(" Examples: socks5://127.0.0.1:1080\n"); + printf(" http://proxy.com:8080:myuser:mypass\n"); + printf(" Default: socks5://127.0.0.1:4444\n\n"); + + printf(" --rule Traffic routing rule (can be specified multiple times)\n"); + printf(" Format: process:hosts:ports:protocol:action\n"); + printf(" process - Process name(s): curl, cur*, *, or multiple separated by ;\n"); + printf(" hosts - IP/host(s): *, google.com, 192.168.*.*, or multiple separated by ; or ,\n"); + printf(" ports - Port(s): *, 443, 80;8080, 80-100, or multiple separated by ; or ,\n"); + printf(" protocol - TCP, UDP, or BOTH\n"); + printf(" action - PROXY, DIRECT, or BLOCK\n"); + printf(" Examples:\n"); + printf(" curl:*:*:TCP:PROXY\n"); + printf(" curl;wget:*:*:TCP:PROXY\n"); + printf(" *:*:53:UDP:PROXY\n"); + printf(" firefox:*:80;443:TCP:DIRECT\n\n"); + + printf(" --dns-via-proxy Route DNS queries through proxy (default: true)\n"); + printf(" --no-dns-via-proxy Do not route DNS queries through proxy\n\n"); + + printf(" --verbose Logging verbosity level\n"); + printf(" 0 - No logs (default)\n"); + printf(" 1 - Show log messages only\n"); + printf(" 2 - Show connection events only\n"); + printf(" 3 - Show both logs and connections\n\n"); + + printf(" --help, -h Show this help message\n\n"); + + printf("EXAMPLES:\n"); + printf(" # Basic usage with default proxy\n"); + printf(" sudo %s --rule curl:*:*:TCP:PROXY\n\n", prog); + + printf(" # Multiple rules with custom proxy\n"); + printf(" sudo %s --proxy socks5://192.168.1.10:1080 \\\n", prog); + printf(" --rule curl:*:*:TCP:PROXY \\\n"); + printf(" --rule wget:*:*:TCP:PROXY \\\n"); + printf(" --verbose 2\n\n"); + + printf(" # Route DNS through proxy with multiple apps\n"); + printf(" sudo %s --proxy socks5://127.0.0.1:1080 \\\n", prog); + printf(" --rule \"curl;wget;firefox:*:*:BOTH:PROXY\" \\\n"); + printf(" --dns-via-proxy --verbose 3\n\n"); + + printf("NOTE:\n"); + printf(" ProxyBridge requires root privileges to use nfqueue.\n"); + printf(" Run with 'sudo' or as root user.\n\n"); +} + +static RuleProtocol parse_protocol(const char* str) +{ + char upper[16]; + for (size_t i = 0; str[i] && i < 15; i++) + upper[i] = toupper(str[i]); + upper[strlen(str) < 15 ? strlen(str) : 15] = '\0'; + + if (strcmp(upper, "TCP") == 0) + return RULE_PROTOCOL_TCP; + else if (strcmp(upper, "UDP") == 0) + return RULE_PROTOCOL_UDP; + else if (strcmp(upper, "BOTH") == 0) + return RULE_PROTOCOL_BOTH; + else + { + fprintf(stderr, "ERROR: Invalid protocol '%s'. Use TCP, UDP, or BOTH\n", str); + exit(1); + } +} + +static RuleAction parse_action(const char* str) +{ + char upper[16]; + for (size_t i = 0; str[i] && i < 15; i++) + upper[i] = toupper(str[i]); + upper[strlen(str) < 15 ? strlen(str) : 15] = '\0'; + + if (strcmp(upper, "PROXY") == 0) + return RULE_ACTION_PROXY; + else if (strcmp(upper, "DIRECT") == 0) + return RULE_ACTION_DIRECT; + else if (strcmp(upper, "BLOCK") == 0) + return RULE_ACTION_BLOCK; + else + { + fprintf(stderr, "ERROR: Invalid action '%s'. Use PROXY, DIRECT, or BLOCK\n", str); + exit(1); + } +} + +static void default_if_empty(char* dest, const char* src, const char* default_val, size_t dest_size) +{ + if (src == NULL || src[0] == '\0' || strcmp(src, " ") == 0) + strncpy(dest, default_val, dest_size - 1); + else + strncpy(dest, src, dest_size - 1); + dest[dest_size - 1] = '\0'; +} + +static bool parse_rule(const char* rule_str, ProxyRule* rule) +{ + char buffer[MAX_RULE_STR]; + strncpy(buffer, rule_str, sizeof(buffer) - 1); + buffer[sizeof(buffer) - 1] = '\0'; + + char* parts[5] = {NULL, NULL, NULL, NULL, NULL}; + int part_idx = 0; + char* token = strtok(buffer, ":"); + + while (token != NULL && part_idx < 5) + { + parts[part_idx++] = token; + token = strtok(NULL, ":"); + } + + if (part_idx != 5) + { + fprintf(stderr, "ERROR: Invalid rule format '%s'\n", rule_str); + fprintf(stderr, "Expected format: process:hosts:ports:protocol:action\n"); + return false; + } + + default_if_empty(rule->process_name, parts[0], "*", sizeof(rule->process_name)); + default_if_empty(rule->target_hosts, parts[1], "*", sizeof(rule->target_hosts)); + default_if_empty(rule->target_ports, parts[2], "*", sizeof(rule->target_ports)); + + rule->protocol = parse_protocol(parts[3]); + rule->action = parse_action(parts[4]); + + return true; +} + +static bool parse_proxy_url(const char* url, ProxyType* type, char* host, uint16_t* port, char* username, char* password) +{ + char buffer[512]; + strncpy(buffer, url, sizeof(buffer) - 1); + buffer[sizeof(buffer) - 1] = '\0'; + + username[0] = '\0'; + password[0] = '\0'; + + // Parse type:// + char* scheme_end = strstr(buffer, "://"); + if (scheme_end == NULL) + { + fprintf(stderr, "ERROR: Invalid proxy URL format. Expected type://host:port\n"); + return false; + } + + *scheme_end = '\0'; + char* scheme = buffer; + char* rest = scheme_end + 3; + + char upper_scheme[16]; + for (size_t i = 0; scheme[i] && i < 15; i++) + upper_scheme[i] = toupper(scheme[i]); + upper_scheme[strlen(scheme) < 15 ? strlen(scheme) : 15] = '\0'; + + if (strcmp(upper_scheme, "SOCKS5") == 0) + *type = PROXY_TYPE_SOCKS5; + else if (strcmp(upper_scheme, "HTTP") == 0) + *type = PROXY_TYPE_HTTP; + else + { + fprintf(stderr, "ERROR: Invalid proxy type '%s'. Use 'socks5' or 'http'\n", scheme); + return false; + } + + // Parse host:port[:username:password] + char* parts[4]; + int num_parts = 0; + char* token = strtok(rest, ":"); + while (token != NULL && num_parts < 4) + { + parts[num_parts++] = token; + token = strtok(NULL, ":"); + } + + if (num_parts < 2) + { + fprintf(stderr, "ERROR: Invalid proxy URL. Missing host or port\n"); + return false; + } + + strncpy(host, parts[0], 255); + host[255] = '\0'; + + *port = atoi(parts[1]); + if (*port == 0) + { + fprintf(stderr, "ERROR: Invalid proxy port '%s'\n", parts[1]); + return false; + } + + if (num_parts >= 4) + { + strncpy(username, parts[2], 255); + username[255] = '\0'; + strncpy(password, parts[3], 255); + password[255] = '\0'; + } + + return true; } -static void print_usage(const char* prog) +static bool is_root(void) { - printf("usage: %s [options]\n", prog); - printf("options:\n"); - printf(" -h proxy server hostname or ip (default: 127.0.0.1)\n"); - printf(" -p proxy server port (default: 1080)\n"); - printf(" -t proxy type: socks5 or http (default: socks5)\n"); - printf(" -u proxy username (optional)\n"); - printf(" -w proxy password (optional)\n"); - printf(" -d disable dns via proxy\n"); - printf(" --help show this help\n"); - printf("\nexample:\n"); - printf(" %s -h 127.0.0.1 -p 1080 -t socks5\n", prog); + return getuid() == 0; } int main(int argc, char *argv[]) { - char proxy_host[256] = "192.168.1.4"; - uint16_t proxy_port = 4444; - ProxyType proxy_type = PROXY_TYPE_SOCKS5; - char username[256] = ""; - char password[256] = ""; + char proxy_url[512] = "socks5://127.0.0.1:4444"; + ProxyRule rules[MAX_RULES]; + int num_rules = 0; bool dns_via_proxy = true; - - // parse args + + // Parse arguments for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--help") == 0) + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { - print_usage(argv[0]); + show_help(argv[0]); return 0; } - else if (strcmp(argv[i], "-h") == 0 && i + 1 < argc) + else if (strcmp(argv[i], "--proxy") == 0 && i + 1 < argc) { - strncpy(proxy_host, argv[++i], sizeof(proxy_host) - 1); + strncpy(proxy_url, argv[++i], sizeof(proxy_url) - 1); + proxy_url[sizeof(proxy_url) - 1] = '\0'; } - else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) + else if (strcmp(argv[i], "--rule") == 0 && i + 1 < argc) { - proxy_port = atoi(argv[++i]); + if (num_rules >= MAX_RULES) + { + fprintf(stderr, "ERROR: Maximum %d rules supported\n", MAX_RULES); + return 1; + } + if (!parse_rule(argv[++i], &rules[num_rules])) + return 1; + num_rules++; } - else if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) + else if (strcmp(argv[i], "--dns-via-proxy") == 0) { - if (strcmp(argv[i + 1], "http") == 0) - proxy_type = PROXY_TYPE_HTTP; - else if (strcmp(argv[i + 1], "socks5") == 0) - proxy_type = PROXY_TYPE_SOCKS5; - i++; + dns_via_proxy = true; } - else if (strcmp(argv[i], "-u") == 0 && i + 1 < argc) + else if (strcmp(argv[i], "--no-dns-via-proxy") == 0) { - strncpy(username, argv[++i], sizeof(username) - 1); + dns_via_proxy = false; } - else if (strcmp(argv[i], "-w") == 0 && i + 1 < argc) + else if (strcmp(argv[i], "--verbose") == 0 && i + 1 < argc) { - strncpy(password, argv[++i], sizeof(password) - 1); + verbose_level = atoi(argv[++i]); + if (verbose_level < 0 || verbose_level > 3) + { + fprintf(stderr, "ERROR: Verbose level must be 0-3\n"); + return 1; + } } - else if (strcmp(argv[i], "-d") == 0) + else { - dns_via_proxy = false; + fprintf(stderr, "ERROR: Unknown option '%s'\n", argv[i]); + fprintf(stderr, "Use --help for usage information\n"); + return 1; } } - - printf("proxybridge cli test\n"); - printf("proxy: %s://%s:%u\n", proxy_type == PROXY_TYPE_HTTP ? "http" : "socks5", - proxy_host, proxy_port); - - // setup callbacks - ProxyBridge_SetLogCallback(log_callback); - ProxyBridge_SetConnectionCallback(connection_callback); - - // configure proxy - if (!ProxyBridge_SetProxyConfig(proxy_type, proxy_host, proxy_port, - username[0] ? username : NULL, - password[0] ? password : NULL)) - { - fprintf(stderr, "failed to configure proxy\n"); - return 1; - } - - ProxyBridge_SetDnsViaProxy(dns_via_proxy); - ProxyBridge_SetTrafficLoggingEnabled(true); - - // add rules - redirect specific apps to proxy - uint32_t rule_id1 = ProxyBridge_AddRule("curl", "*", "*", RULE_PROTOCOL_TCP, RULE_ACTION_PROXY); - if (rule_id1 == 0) + + show_banner(); + + // Check root privileges + if (!is_root()) { - fprintf(stderr, "failed to add curl rule\n"); + printf("\033[31m\nERROR: ProxyBridge requires root privileges!\033[0m\n"); + printf("Please run this application with sudo or as root.\n\n"); return 1; } - printf("added rule id %u: curl tcp traffic via proxy\n", rule_id1); - - uint32_t rule_id2 = ProxyBridge_AddRule("nghttp", "*", "*", RULE_PROTOCOL_BOTH, RULE_ACTION_PROXY); - if (rule_id2 == 0) - { - fprintf(stderr, "failed to add nghttp rule\n"); + + // Parse proxy configuration + ProxyType proxy_type; + char proxy_host[256]; + uint16_t proxy_port; + char proxy_username[256]; + char proxy_password[256]; + + if (!parse_proxy_url(proxy_url, &proxy_type, proxy_host, &proxy_port, proxy_username, proxy_password)) return 1; - } - printf("added rule id %u: nghttp tcp+udp traffic via proxy\n", rule_id2); - - uint32_t rule_id3 = ProxyBridge_AddRule("http3-client", "*", "*", RULE_PROTOCOL_UDP, RULE_ACTION_PROXY); - if (rule_id3 == 0) + + // Setup callbacks based on verbose level + if (verbose_level == 1 || verbose_level == 3) + ProxyBridge_SetLogCallback(log_callback); + + if (verbose_level == 2 || verbose_level == 3) + ProxyBridge_SetConnectionCallback(connection_callback); + + // Enable traffic logging only if needed + ProxyBridge_SetTrafficLoggingEnabled(verbose_level > 0); + + // Display configuration + printf("Proxy: %s://%s:%u\n", + proxy_type == PROXY_TYPE_HTTP ? "http" : "socks5", + proxy_host, proxy_port); + + if (proxy_username[0] != '\0') + printf("Proxy Auth: %s:***\n", proxy_username); + + printf("DNS via Proxy: %s\n", dns_via_proxy ? "Enabled" : "Disabled"); + + // Configure proxy + if (!ProxyBridge_SetProxyConfig(proxy_type, proxy_host, proxy_port, + proxy_username[0] ? proxy_username : "", + proxy_password[0] ? proxy_password : "")) { - fprintf(stderr, "failed to add http3-client rule\n"); + fprintf(stderr, "ERROR: Failed to set proxy configuration\n"); return 1; } - printf("added rule id %u: http3-client udp traffic via proxy\n", rule_id3); - - // test connection - char test_result[2048]; - printf("\ntesting proxy connection\n"); - if (ProxyBridge_TestConnection("www.google.com", 80, test_result, sizeof(test_result)) == 0) + + ProxyBridge_SetDnsViaProxy(dns_via_proxy); + + // Add rules + if (num_rules > 0) { - printf("%s\n", test_result); + printf("Rules: %d\n", num_rules); + for (int i = 0; i < num_rules; i++) + { + const char* protocol_str = rules[i].protocol == RULE_PROTOCOL_TCP ? "TCP" : + rules[i].protocol == RULE_PROTOCOL_UDP ? "UDP" : "BOTH"; + const char* action_str = rules[i].action == RULE_ACTION_PROXY ? "PROXY" : + rules[i].action == RULE_ACTION_DIRECT ? "DIRECT" : "BLOCK"; + + uint32_t rule_id = ProxyBridge_AddRule( + rules[i].process_name, + rules[i].target_hosts, + rules[i].target_ports, + rules[i].protocol, + rules[i].action); + + if (rule_id > 0) + { + printf(" [%u] %s:%s:%s:%s -> %s\n", + rule_id, + rules[i].process_name, + rules[i].target_hosts, + rules[i].target_ports, + protocol_str, + action_str); + } + else + { + fprintf(stderr, " ERROR: Failed to add rule for %s\n", rules[i].process_name); + } + } } else { - printf("connection test failed:\n%s\n", test_result); - return 1; + printf("\033[33mWARNING: No rules specified. No traffic will be proxied.\033[0m\n"); + printf("Use --rule to add proxy rules. See --help for examples.\n"); } - - // start service - printf("\nstarting proxybridge\n"); - printf("note: requires root privileges for nfqueue\n"); - if (getuid() != 0) - { - fprintf(stderr, "error: must run as root\n"); - return 1; - } - + // Start ProxyBridge if (!ProxyBridge_Start()) { - fprintf(stderr, "failed to start proxybridge\n"); + fprintf(stderr, "ERROR: Failed to start ProxyBridge\n"); return 1; } - - printf("proxybridge running - press ctrl+c to stop\n"); - + + keep_running = true; + printf("\nProxyBridge started. Press Ctrl+C to stop...\n\n"); + signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); - - // main loop + signal(SIGSEGV, signal_handler); // Catch segfault + signal(SIGABRT, signal_handler); // Catch abort + signal(SIGBUS, signal_handler); // Catch bus error + + // Main loop while (keep_running) { sleep(1); } - - printf("\nstopping proxybridge\n"); + + // Cleanup ProxyBridge_Stop(); - - printf("cleanup completed\n"); + printf("ProxyBridge stopped.\n"); + return 0; } diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index 9f981a4..8433bb7 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -92,6 +94,18 @@ static LOGGED_CONNECTION *logged_connections = NULL; static PROCESS_RULE *rules_list = NULL; static uint32_t g_next_rule_id = 1; static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +typedef struct { + int client_socket; + uint32_t orig_dest_ip; + uint16_t orig_dest_port; +} connection_config_t; + +typedef struct { + int from_socket; + int to_socket; +} transfer_config_t; + static struct nfq_handle *nfq_h = NULL; static struct nfq_q_handle *nfq_qh = NULL; static pthread_t packet_thread[NUM_PACKET_THREADS] = {0}; @@ -952,114 +966,153 @@ static int http_connect(int s, uint32_t dest_ip, uint16_t dest_port) return 0; } -static void* transfer_handler(void *arg) -{ - TRANSFER_CONFIG *config = arg; - char buffer[65536]; - ssize_t n; - - while ((n = recv(config->from_socket, buffer, sizeof(buffer), 0)) > 0) - { - if (send_all(config->to_socket, buffer, n) < 0) - break; - } - - shutdown(config->from_socket, SHUT_RD); - shutdown(config->to_socket, SHUT_WR); - free(config); - return NULL; -} +// ===== EPOLL RELAY FUNCTIONS (Production) ===== +// Windows-style connection handler - blocks on connect and handshake, then calls transfer_handler static void* connection_handler(void *arg) { - CONNECTION_CONFIG *config = arg; + connection_config_t *config = (connection_config_t *)arg; int client_sock = config->client_socket; uint32_t dest_ip = config->orig_dest_ip; uint16_t dest_port = config->orig_dest_port; - int socks_sock; - struct sockaddr_in socks_addr; - uint32_t socks5_ip; + int proxy_sock; + struct sockaddr_in proxy_addr; + uint32_t proxy_ip; free(config); - socks5_ip = resolve_hostname(g_proxy_host); - if (socks5_ip == 0) + // Connect to proxy + proxy_ip = resolve_hostname(g_proxy_host); + if (proxy_ip == 0) { close(client_sock); return NULL; } - socks_sock = socket(AF_INET, SOCK_STREAM, 0); - if (socks_sock < 0) + proxy_sock = socket(AF_INET, SOCK_STREAM, 0); + if (proxy_sock < 0) { - log_message("socket creation failed %d", errno); close(client_sock); return NULL; } - configure_tcp_socket(socks_sock, 524288, 30000); + configure_tcp_socket(proxy_sock, 524288, 30000); configure_tcp_socket(client_sock, 524288, 30000); - memset(&socks_addr, 0, sizeof(socks_addr)); - socks_addr.sin_family = AF_INET; - socks_addr.sin_addr.s_addr = socks5_ip; - socks_addr.sin_port = htons(g_proxy_port); + memset(&proxy_addr, 0, sizeof(proxy_addr)); + proxy_addr.sin_family = AF_INET; + proxy_addr.sin_addr.s_addr = proxy_ip; + proxy_addr.sin_port = htons(g_proxy_port); - if (connect(socks_sock, (struct sockaddr *)&socks_addr, sizeof(socks_addr)) < 0) + if (connect(proxy_sock, (struct sockaddr *)&proxy_addr, sizeof(proxy_addr)) < 0) { - log_message("failed to connect to proxy %d", errno); close(client_sock); - close(socks_sock); + close(proxy_sock); return NULL; } + // Do blocking handshake if (g_proxy_type == PROXY_TYPE_SOCKS5) { - if (socks5_connect(socks_sock, dest_ip, dest_port) != 0) + if (socks5_connect(proxy_sock, dest_ip, dest_port) != 0) { close(client_sock); - close(socks_sock); + close(proxy_sock); return NULL; } } else if (g_proxy_type == PROXY_TYPE_HTTP) { - if (http_connect(socks_sock, dest_ip, dest_port) != 0) + if (http_connect(proxy_sock, dest_ip, dest_port) != 0) { close(client_sock); - close(socks_sock); + close(proxy_sock); return NULL; } } - TRANSFER_CONFIG *client_to_socks = malloc(sizeof(TRANSFER_CONFIG)); - TRANSFER_CONFIG *socks_to_client = malloc(sizeof(TRANSFER_CONFIG)); - - if (!client_to_socks || !socks_to_client) + // Create transfer config + transfer_config_t *transfer_config = (transfer_config_t *)malloc(sizeof(transfer_config_t)); + if (transfer_config == NULL) { close(client_sock); - close(socks_sock); - free(client_to_socks); - free(socks_to_client); + close(proxy_sock); return NULL; } - client_to_socks->from_socket = client_sock; - client_to_socks->to_socket = socks_sock; - socks_to_client->from_socket = socks_sock; - socks_to_client->to_socket = client_sock; + transfer_config->from_socket = client_sock; + transfer_config->to_socket = proxy_sock; + + // Do bidirectional transfer in this thread (like Windows) + transfer_handler((void*)transfer_config); - pthread_t transfer_thread; - pthread_create(&transfer_thread, NULL, transfer_handler, client_to_socks); - pthread_detach(transfer_thread); + return NULL; +} + +// Windows-style transfer handler - uses select() to monitor both sockets +static void* transfer_handler(void *arg) +{ + transfer_config_t *config = (transfer_config_t *)arg; + int sock1 = config->from_socket; // client socket + int sock2 = config->to_socket; // proxy socket + char buf[131072]; // 128KB buffer + int len; + + free(config); + + // Monitor BOTH sockets in one thread (like Windows) + while (1) + { + fd_set readfds; + struct timeval timeout; + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); // client + FD_SET(sock2, &readfds); // proxy + + timeout.tv_sec = 0; + timeout.tv_usec = 50000; // 50ms + + int ready = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout); + + if (ready < 0) + break; + + if (ready == 0) + continue; - transfer_handler(socks_to_client); + // Check client to proxy + if (FD_ISSET(sock1, &readfds)) + { + len = recv(sock1, buf, sizeof(buf), 0); + if (len <= 0) + break; + + if (send_all(sock2, buf, len) < 0) + break; + } + + // Check proxy to client + if (FD_ISSET(sock2, &readfds)) + { + len = recv(sock2, buf, sizeof(buf), 0); + if (len <= 0) + break; - close(client_sock); - close(socks_sock); + if (send_all(sock1, buf, len) < 0) + break; + } + } + + // Cleanup + shutdown(sock1, SHUT_RDWR); + shutdown(sock2, SHUT_RDWR); + close(sock1); + close(sock2); return NULL; } +// Windows-style proxy server - accepts and spawns thread per connection static void* local_proxy_server(void *arg) { (void)arg; @@ -1070,14 +1123,12 @@ static void* local_proxy_server(void *arg) listen_sock = socket(AF_INET, SOCK_STREAM, 0); if (listen_sock < 0) { - log_message("socket creation failed %d", errno); + log_message("Socket creation failed"); return NULL; } setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); - - int nodelay = 1; - setsockopt(listen_sock, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); + setsockopt(listen_sock, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; @@ -1086,19 +1137,19 @@ static void* local_proxy_server(void *arg) if (bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - log_message("bind failed %d", errno); + log_message("Bind failed"); close(listen_sock); return NULL; } if (listen(listen_sock, SOMAXCONN) < 0) { - log_message("listen failed %d", errno); + log_message("Listen failed"); close(listen_sock); return NULL; } - log_message("local proxy listening on port %d", g_local_relay_port); + log_message("Local proxy listening on port %d", g_local_relay_port); while (running) { @@ -1107,7 +1158,7 @@ static void* local_proxy_server(void *arg) FD_SET(listen_sock, &read_fds); struct timeval timeout = {1, 0}; - if (select(listen_sock + 1, &read_fds, NULL, NULL, &timeout) <= 0) + if (select(FD_SETSIZE, &read_fds, NULL, NULL, &timeout) <= 0) continue; struct sockaddr_in client_addr; @@ -1117,10 +1168,7 @@ static void* local_proxy_server(void *arg) if (client_sock < 0) continue; - log_message("[RELAY] accepted connection from %s:%u", - inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); - - CONNECTION_CONFIG *conn_config = malloc(sizeof(CONNECTION_CONFIG)); + connection_config_t *conn_config = (connection_config_t *)malloc(sizeof(connection_config_t)); if (conn_config == NULL) { close(client_sock); @@ -1132,20 +1180,14 @@ static void* local_proxy_server(void *arg) uint16_t client_port = ntohs(client_addr.sin_port); if (!get_connection(client_port, &conn_config->orig_dest_ip, &conn_config->orig_dest_port)) { - log_message("[RELAY] no connection tracking for port %u", client_port); close(client_sock); free(conn_config); continue; } - - char dest_str[32]; - format_ip_address(conn_config->orig_dest_ip, dest_str, sizeof(dest_str)); - log_message("[RELAY] forwarding to %s:%u via proxy", dest_str, conn_config->orig_dest_port); pthread_t conn_thread; - if (pthread_create(&conn_thread, NULL, connection_handler, conn_config) != 0) + if (pthread_create(&conn_thread, NULL, connection_handler, (void*)conn_config) != 0) { - log_message("pthread_create failed %d", errno); close(client_sock); free(conn_config); continue; From 0a0b3bae6f66a8c310a675dcf2360398756f6566 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Wed, 4 Feb 2026 13:13:01 +0530 Subject: [PATCH 08/58] 4.0 beta - added cleanup option --- Linux/cli/main.c | 17 ++++++++++++++++- Linux/src/ProxyBridge.c | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Linux/cli/main.c b/Linux/cli/main.c index eee7b8d..1ed0e04 100644 --- a/Linux/cli/main.c +++ b/Linux/cli/main.c @@ -66,7 +66,7 @@ static void show_banner(void) printf(" | |_) | '__/ _ \\ \\/ / | | | | _ \\| '__| |/ _` |/ _` |/ _ \\\n"); printf(" | __/| | | (_) > <| |_| | | |_) | | | | (_| | (_| | __/\n"); printf(" |_| |_| \\___/_/\\_\\\\__, | |____/|_| |_|\\__,_|\\__, |\\___|\n"); - printf(" |___/ |___/ V3.1.0\n"); + printf(" |___/ |___/ V4.0-Beta\n"); printf("\n"); printf(" Universal proxy client for Linux applications\n"); printf("\n"); @@ -110,6 +110,9 @@ static void show_help(const char* prog) printf(" 2 - Show connection events only\n"); printf(" 3 - Show both logs and connections\n\n"); + printf(" --cleanup Cleanup resources (iptables, etc.) from crashed instance\n"); + printf(" Use if ProxyBridge crashed without proper cleanup\n\n"); + printf(" --help, -h Show this help message\n\n"); printf("EXAMPLES:\n"); @@ -294,6 +297,18 @@ static bool is_root(void) int main(int argc, char *argv[]) { + // Check for cleanup flag first - ignores all other args + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--cleanup") == 0) + { + printf("Running cleanup...\n"); + ProxyBridge_Stop(); + printf("Cleanup complete.\n"); + return 0; + } + } + char proxy_url[512] = "socks5://127.0.0.1:4444"; ProxyRule rules[MAX_RULES]; int num_rules = 0; diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index 8433bb7..4fdf54e 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -26,6 +26,7 @@ #define LOCAL_PROXY_PORT 34010 #define LOCAL_UDP_RELAY_PORT 34011 #define MAX_PROCESS_NAME 256 +#define VERSION "4.0-Beta" #define PID_CACHE_SIZE 1024 #define PID_CACHE_TTL_MS 1000 #define NUM_PACKET_THREADS 4 From 02135466ad4735f78c88d29932176370005b12ca Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Wed, 4 Feb 2026 13:29:18 +0530 Subject: [PATCH 09/58] updated cli file name. added deploy script --- Linux/build.sh | 5 +- Linux/cli/Makefile | 5 +- Linux/deploy.sh | 202 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 Linux/deploy.sh diff --git a/Linux/build.sh b/Linux/build.sh index 95faec7..868b8b4 100755 --- a/Linux/build.sh +++ b/Linux/build.sh @@ -35,7 +35,7 @@ cd "$SCRIPT_DIR/cli" make clean 2>/dev/null || true make -if [ -f "proxybridge-cli" ]; then +if [ -f "ProxyBridge" ]; then echo "✓ CLI build successful" else echo "✗ CLI build failed!" @@ -46,7 +46,7 @@ echo "" # Move binaries to output echo "Moving binaries to output directory..." mv "$SCRIPT_DIR/src/libproxybridge.so" "$OUTPUT_DIR/" -mv proxybridge-cli "$OUTPUT_DIR/" +mv ProxyBridge "$OUTPUT_DIR/" echo "✓ Binaries moved to output" echo "" @@ -69,3 +69,4 @@ cd "$OUTPUT_DIR" ls -lh echo "" echo "Output location: $OUTPUT_DIR" + diff --git a/Linux/cli/Makefile b/Linux/cli/Makefile index d392069..6aff0ac 100644 --- a/Linux/cli/Makefile +++ b/Linux/cli/Makefile @@ -8,10 +8,11 @@ CFLAGS = -Wall -Wextra -O3 -I../src \ -fno-strict-overflow \ -fno-delete-null-pointer-checks \ -fwrapv -LDFLAGS = -L../src -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s +LDFLAGS = -L../src -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s \ + -Wl,-rpath,/usr/local/lib LIBS = -lproxybridge -lpthread -TARGET = proxybridge-cli +TARGET = ProxyBridge SOURCES = main.c OBJECTS = $(SOURCES:.c=.o) diff --git a/Linux/deploy.sh b/Linux/deploy.sh new file mode 100644 index 0000000..0b75627 --- /dev/null +++ b/Linux/deploy.sh @@ -0,0 +1,202 @@ +#!/bin/bash + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "" +echo "===================================" +echo "ProxyBridge Deployment Script" +echo "===================================" +echo "" + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo "ERROR: This script must be run as root" + echo "Please run: sudo ./deploy.sh" + exit 1 +fi + +# Detect Linux distribution +detect_distro() { + if [ -f /etc/os-release ]; then + . /etc/os-release + DISTRO=$ID + DISTRO_LIKE=$ID_LIKE + elif [ -f /etc/lsb-release ]; then + . /etc/lsb-release + DISTRO=$DISTRIB_ID + else + DISTRO=$(uname -s) + fi + echo "Detected distribution: $DISTRO" +} + +# Install dependencies based on distribution +install_dependencies() { + echo "" + echo "Checking and installing dependencies..." + + case "$DISTRO" in + ubuntu|debian) + echo "Using apt package manager..." + apt-get update -qq + apt-get install -y libnetfilter-queue1 libnetfilter-queue-dev libnfnetlink0 iptables + ;; + fedora) + echo "Using dnf package manager..." + dnf install -y libnetfilter_queue libnetfilter_queue-devel libnfnetlink iptables + ;; + rhel|centos|rocky|almalinux) + echo "Using yum package manager..." + yum install -y libnetfilter_queue libnetfilter_queue-devel libnfnetlink iptables + ;; + arch|manjaro) + echo "Using pacman package manager..." + pacman -Sy --noconfirm libnetfilter_queue libnfnetlink iptables + ;; + opensuse*|sles) + echo "Using zypper package manager..." + zypper install -y libnetfilter_queue1 libnetfilter_queue-devel libnfnetlink0 iptables + ;; + *) + echo "WARNING: Unknown distribution '$DISTRO'" + echo "Please manually install: libnetfilter-queue, libnfnetlink, iptables" + read -p "Continue anyway? (y/n) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + ;; + esac + + echo "Dependencies installed" +} + +# Use /usr/local/lib (matches RPATH in binary) +detect_lib_path() { + LIB_PATH="/usr/local/lib" + echo "Library installation path: $LIB_PATH" +} + +# Check if files exist in current directory +check_files() { + echo "" + echo "Checking for required files..." + + if [ ! -f "$SCRIPT_DIR/libproxybridge.so" ]; then + echo "ERROR: libproxybridge.so not found in $SCRIPT_DIR" + exit 1 + fi + + if [ ! -f "$SCRIPT_DIR/ProxyBridge" ]; then + echo "ERROR: ProxyBridge binary not found in $SCRIPT_DIR" + exit 1 + fi + + echo "All files present" +} + +# Install files +install_files() { + echo "" + echo "Installing ProxyBridge..." + + # Create directories if they don't exist + mkdir -p "$LIB_PATH" + mkdir -p /usr/local/bin + + # Copy library + echo "Installing libproxybridge.so to $LIB_PATH..." + cp "$SCRIPT_DIR/libproxybridge.so" "$LIB_PATH/" + chmod 755 "$LIB_PATH/libproxybridge.so" + + # Copy binary + echo "Installing ProxyBridge to /usr/local/bin..." + cp "$SCRIPT_DIR/ProxyBridge" /usr/local/bin/ + chmod 755 /usr/local/bin/ProxyBridge + + echo "Files installed" +} + +# Update library cache +update_ldconfig() { + echo "" + echo "Updating library cache..." + + # Add /usr/local/lib to ld.so.conf if not already there + if [ -d /etc/ld.so.conf.d ]; then + if ! grep -q "^/usr/local/lib" /etc/ld.so.conf.d/* 2>/dev/null; then + echo "/usr/local/lib" > /etc/ld.so.conf.d/proxybridge.conf + if [ "$LIB_PATH" = "/usr/local/lib64" ]; then + echo "/usr/local/lib64" >> /etc/ld.so.conf.d/proxybridge.conf + fi + fi + fi + + # Run ldconfig + if command -v ldconfig &> /dev/null; then + ldconfig 2>/dev/null || true + # Also ensure cache is regenerated + ldconfig -v 2>/dev/null | grep -q proxybridge || true + echo "Library cache updated" + else + echo "WARNING: ldconfig not found. You may need to reboot." + fi +} + +# Verify installation +verify_installation() { + echo "" + echo "Verifying installation..." + + # Check if binary is in PATH + if command -v ProxyBridge &> /dev/null; then + echo "ProxyBridge binary found in PATH" + else + echo "ProxyBridge binary not found in PATH" + echo " You may need to add /usr/local/bin to your PATH" + fi + + # Check if library is loadable + if ldd /usr/local/bin/ProxyBridge 2>/dev/null | grep -q "libproxybridge.so"; then + if ldd /usr/local/bin/ProxyBridge 2>/dev/null | grep "libproxybridge.so" | grep -q "not found"; then + echo "libproxybridge.so not loadable" + else + echo "libproxybridge.so is loadable" + fi + fi + + # Final test - try to run --help + if /usr/local/bin/ProxyBridge --help &>/dev/null; then + echo "ProxyBridge executable is working" + else + echo "ProxyBridge may have issues - try: sudo ldconfig" + fi +} + +# Main deployment +main() { + detect_distro + check_files + install_dependencies + detect_lib_path + install_files + update_ldconfig + verify_installation + + echo "" + echo "===================================" + echo "Installation Complete!" + echo "===================================" + echo "" + echo "You can now run ProxyBridge from anywhere:" + echo " sudo ProxyBridge --help" + echo " sudo ProxyBridge --proxy socks5://IP:PORT --rule \"app:*:*:TCP:PROXY\"" + echo "" + echo "For cleanup after crash:" + echo " sudo ProxyBridge --cleanup" + echo "" +} + +main From cd850bb0e131a3cfba0c3f1f458de836eb83af88 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Wed, 4 Feb 2026 13:43:32 +0530 Subject: [PATCH 10/58] removed unwatned code and logs --- Linux/cli/main.c | 37 ++++++++++++++++++++++++++----------- Linux/src/ProxyBridge.c | 12 ------------ 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Linux/cli/main.c b/Linux/cli/main.c index 1ed0e04..b8807de 100644 --- a/Linux/cli/main.c +++ b/Linux/cli/main.c @@ -101,8 +101,9 @@ static void show_help(const char* prog) printf(" *:*:53:UDP:PROXY\n"); printf(" firefox:*:80;443:TCP:DIRECT\n\n"); - printf(" --dns-via-proxy Route DNS queries through proxy (default: true)\n"); - printf(" --no-dns-via-proxy Do not route DNS queries through proxy\n\n"); + printf(" --dns-via-proxy Route DNS queries through proxy\n"); + printf(" Values: true, false, 1, 0\n"); + printf(" Default: true\n\n"); printf(" --verbose Logging verbosity level\n"); printf(" 0 - No logs (default)\n"); @@ -128,7 +129,7 @@ static void show_help(const char* prog) printf(" # Route DNS through proxy with multiple apps\n"); printf(" sudo %s --proxy socks5://127.0.0.1:1080 \\\n", prog); printf(" --rule \"curl;wget;firefox:*:*:BOTH:PROXY\" \\\n"); - printf(" --dns-via-proxy --verbose 3\n\n"); + printf(" --dns-via-proxy true --verbose 3\n\n"); printf("NOTE:\n"); printf(" ProxyBridge requires root privileges to use nfqueue.\n"); @@ -338,13 +339,18 @@ int main(int argc, char *argv[]) return 1; num_rules++; } - else if (strcmp(argv[i], "--dns-via-proxy") == 0) + else if (strcmp(argv[i], "--dns-via-proxy") == 0 && i + 1 < argc) { - dns_via_proxy = true; - } - else if (strcmp(argv[i], "--no-dns-via-proxy") == 0) - { - dns_via_proxy = false; + char* value = argv[++i]; + if (strcmp(value, "true") == 0 || strcmp(value, "1") == 0) + dns_via_proxy = true; + else if (strcmp(value, "false") == 0 || strcmp(value, "0") == 0) + dns_via_proxy = false; + else + { + fprintf(stderr, "ERROR: Invalid value for --dns-via-proxy. Use: true, false, 1, or 0\n"); + return 1; + } } else if (strcmp(argv[i], "--verbose") == 0 && i + 1 < argc) { @@ -383,14 +389,23 @@ int main(int argc, char *argv[]) if (!parse_proxy_url(proxy_url, &proxy_type, proxy_host, &proxy_port, proxy_username, proxy_password)) return 1; - // Setup callbacks based on verbose level + // Setup callbacks based on verbose level - only enable what we need + // Verbose 0: No callbacks, no logging (most efficient) + // Verbose 1: Only log messages + // Verbose 2: Only connection events + // Verbose 3: Both logs and connections + if (verbose_level == 1 || verbose_level == 3) ProxyBridge_SetLogCallback(log_callback); + else + ProxyBridge_SetLogCallback(NULL); // Explicitly disable if (verbose_level == 2 || verbose_level == 3) ProxyBridge_SetConnectionCallback(connection_callback); + else + ProxyBridge_SetConnectionCallback(NULL); // Explicitly disable - // Enable traffic logging only if needed + // Enable traffic logging in C library only when needed (prevents unnecessary processing) ProxyBridge_SetTrafficLoggingEnabled(verbose_level > 0); // Display configuration diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index 4fdf54e..5e7e818 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -684,10 +684,6 @@ static uint32_t resolve_hostname(const char *hostname) uint32_t resolved_ip = addr->sin_addr.s_addr; freeaddrinfo(result); - log_message("resolved %s to %d.%d.%d.%d", hostname, - (resolved_ip >> 0) & 0xFF, (resolved_ip >> 8) & 0xFF, - (resolved_ip >> 16) & 0xFF, (resolved_ip >> 24) & 0xFF); - return resolved_ip; } @@ -1150,8 +1146,6 @@ static void* local_proxy_server(void *arg) return NULL; } - log_message("Local proxy listening on port %d", g_local_relay_port); - while (running) { fd_set read_fds; @@ -1332,7 +1326,6 @@ static bool establish_udp_associate(void) configure_udp_socket(socks5_udp_send_socket, 262144, 30000); udp_associate_connected = true; - log_message("[UDP] ASSOCIATE established with SOCKS5 proxy"); return true; } @@ -1366,10 +1359,6 @@ static void* udp_relay_server(void *arg) udp_associate_connected = establish_udp_associate(); - log_message("[UDP] relay listening on port %d", LOCAL_UDP_RELAY_PORT); - if (!udp_associate_connected) - log_message("[UDP] ASSOCIATE not available yet - will retry when needed"); - while (running) { fd_set read_fds; @@ -1603,7 +1592,6 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru add_connection(src_port, src_ip, dest_ip, dest_port); // mark packet so nat table REDIRECT rule will catch it - log_message("[REDIRECT] marking packet sport %u for redirect", src_port); uint32_t mark = 1; return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL); } From ed3b8ecfeac2471e04e59bda832eca6764b82a1b Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 14:31:16 +0530 Subject: [PATCH 11/58] improved Linux Performance, added validation, fixed pid lookup, cache --- Linux/deploy.sh | 39 +++- Linux/gui/main.c | 152 +++++++++++++ Linux/src/ProxyBridge.c | 478 +++++++++++++++++++--------------------- 3 files changed, 412 insertions(+), 257 deletions(-) create mode 100644 Linux/gui/main.c diff --git a/Linux/deploy.sh b/Linux/deploy.sh index 0b75627..2844358 100644 --- a/Linux/deploy.sh +++ b/Linux/deploy.sh @@ -37,31 +37,52 @@ install_dependencies() { echo "" echo "Checking and installing dependencies..." - case "$DISTRO" in - ubuntu|debian) + # Normalize distro name using ID_LIKE fallback + local distro_family="$DISTRO" + if [ -n "$DISTRO_LIKE" ]; then + case "$DISTRO_LIKE" in + *ubuntu*|*debian*) distro_family="debian" ;; + *fedora*) distro_family="fedora" ;; + *rhel*|*centos*) distro_family="rhel" ;; + *arch*) distro_family="arch" ;; + *suse*) distro_family="opensuse" ;; + esac + fi + + case "$distro_family" in + ubuntu|debian|linuxmint|pop|elementary|zorin|kali|raspbian|mx|antix|deepin|lmde) echo "Using apt package manager..." apt-get update -qq - apt-get install -y libnetfilter-queue1 libnetfilter-queue-dev libnfnetlink0 iptables + apt-get install -y libnetfilter-queue1 libnfnetlink0 iptables ;; fedora) echo "Using dnf package manager..." - dnf install -y libnetfilter_queue libnetfilter_queue-devel libnfnetlink iptables + dnf install -y libnetfilter_queue libnfnetlink iptables ;; rhel|centos|rocky|almalinux) echo "Using yum package manager..." - yum install -y libnetfilter_queue libnetfilter_queue-devel libnfnetlink iptables + yum install -y libnetfilter_queue libnfnetlink iptables ;; - arch|manjaro) + arch|manjaro|endeavouros|garuda) echo "Using pacman package manager..." pacman -Sy --noconfirm libnetfilter_queue libnfnetlink iptables ;; opensuse*|sles) echo "Using zypper package manager..." - zypper install -y libnetfilter_queue1 libnetfilter_queue-devel libnfnetlink0 iptables + zypper install -y libnetfilter_queue1 libnfnetlink0 iptables + ;; + void) + echo "Using xbps package manager..." + xbps-install -Sy libnetfilter_queue libnfnetlink iptables ;; *) - echo "WARNING: Unknown distribution '$DISTRO'" - echo "Please manually install: libnetfilter-queue, libnfnetlink, iptables" + echo "WARNING: Unknown distribution '$DISTRO' (family: '$DISTRO_LIKE')" + echo "" + echo "Please manually install the following packages:" + echo " Debian/Ubuntu: sudo apt install libnetfilter-queue1 libnfnetlink0 iptables" + echo " Fedora: sudo dnf install libnetfilter_queue libnfnetlink iptables" + echo " Arch: sudo pacman -S libnetfilter_queue libnfnetlink iptables" + echo "" read -p "Continue anyway? (y/n) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then diff --git a/Linux/gui/main.c b/Linux/gui/main.c new file mode 100644 index 0000000..399f27b --- /dev/null +++ b/Linux/gui/main.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include "proxybridge.h" +#include "main_window.h" + +// Library handle +static void *lib_handle = NULL; + +// Function pointers +uint32_t (*ProxyBridge_AddRule)(const char *, const char *, const char *, RuleProtocol, RuleAction) = NULL; +bool (*ProxyBridge_EnableRule)(uint32_t) = NULL; +bool (*ProxyBridge_DisableRule)(uint32_t) = NULL; +bool (*ProxyBridge_DeleteRule)(uint32_t) = NULL; +bool (*ProxyBridge_EditRule)(uint32_t, const char *, const char *, const char *, RuleProtocol, RuleAction) = NULL; +bool (*ProxyBridge_SetProxyConfig)(ProxyType, const char *, uint16_t, const char *, const char *) = NULL; +void (*ProxyBridge_SetLogCallback)(LogCallback) = NULL; +void (*ProxyBridge_SetConnectionCallback)(ConnectionCallback) = NULL; +void (*ProxyBridge_SetTrafficLoggingEnabled)(bool) = NULL; +void (*ProxyBridge_SetDnsViaProxy)(bool) = NULL; +bool (*ProxyBridge_Start)(void) = NULL; +bool (*ProxyBridge_Stop)(void) = NULL; + +// Callback handlers +static void log_callback(const char *message) { + add_activity_log(message); +} + +static void connection_callback(const char *processName, uint32_t pid, + const char *destIp, uint16_t destPort, + const char *proxyInfo) { + char msg[512]; + snprintf(msg, sizeof(msg), "%s (PID %u) → %s:%u via %s", + processName, pid, destIp, destPort, proxyInfo); + add_connection_log(msg); +} + +bool load_proxybridge_library(void) +{ + lib_handle = dlopen("libproxybridge.so", RTLD_LAZY); + if (!lib_handle) { + lib_handle = dlopen("/usr/local/lib/libproxybridge.so", RTLD_LAZY); + } + + if (!lib_handle) { + fprintf(stderr, "Failed to load libproxybridge.so: %s\n", dlerror()); + return false; + } + + #define LOAD_SYMBOL(name) \ + name = dlsym(lib_handle, #name); \ + if (!name) { \ + fprintf(stderr, "Failed to load symbol %s: %s\n", #name, dlerror()); \ + dlclose(lib_handle); \ + return false; \ + } + + LOAD_SYMBOL(ProxyBridge_AddRule); + LOAD_SYMBOL(ProxyBridge_EnableRule); + LOAD_SYMBOL(ProxyBridge_DisableRule); + LOAD_SYMBOL(ProxyBridge_DeleteRule); + LOAD_SYMBOL(ProxyBridge_EditRule); + LOAD_SYMBOL(ProxyBridge_SetProxyConfig); + LOAD_SYMBOL(ProxyBridge_SetLogCallback); + LOAD_SYMBOL(ProxyBridge_SetConnectionCallback); + LOAD_SYMBOL(ProxyBridge_SetTrafficLoggingEnabled); + LOAD_SYMBOL(ProxyBridge_SetDnsViaProxy); + LOAD_SYMBOL(ProxyBridge_Start); + LOAD_SYMBOL(ProxyBridge_Stop); + + #undef LOAD_SYMBOL + + // Set up callbacks + ProxyBridge_SetLogCallback(log_callback); + ProxyBridge_SetConnectionCallback(connection_callback); + + return true; +} + +void unload_proxybridge_library(void) +{ + if (lib_handle) { + // Stop proxy before unloading (critical cleanup) + if (ProxyBridge_Stop) { + ProxyBridge_Stop(); + } + dlclose(lib_handle); + lib_handle = NULL; + } +} + +static gboolean check_root_privileges(void) +{ + if (geteuid() != 0) { + GtkWidget *dialog = gtk_message_dialog_new( + NULL, + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "ProxyBridge requires root privileges" + ); + gtk_message_dialog_format_secondary_text( + GTK_MESSAGE_DIALOG(dialog), + "Please run with: sudo ./ProxyBridge-GUI" + ); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return FALSE; + } + return TRUE; +} + +int main(int argc, char *argv[]) +{ + gtk_init(&argc, &argv); + + // Check root privileges + if (!check_root_privileges()) { + return 1; + } + + // Load ProxyBridge library + if (!load_proxybridge_library()) { + GtkWidget *dialog = gtk_message_dialog_new( + NULL, + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "Failed to load ProxyBridge library" + ); + gtk_message_dialog_format_secondary_text( + GTK_MESSAGE_DIALOG(dialog), + "Make sure libproxybridge.so is in /usr/local/lib/" + ); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return 1; + } + + // Create main window + create_main_window(); + + // Run GTK main loop + gtk_main(); + + // Cleanup + unload_proxybridge_library(); + + return 0; +} diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index 5e7e818..5887c4f 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -8,8 +8,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -94,7 +96,9 @@ static CONNECTION_INFO *connection_hash_table[CONNECTION_HASH_SIZE] = {NULL}; static LOGGED_CONNECTION *logged_connections = NULL; static PROCESS_RULE *rules_list = NULL; static uint32_t g_next_rule_id = 1; -static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_rwlock_t conn_lock = PTHREAD_RWLOCK_INITIALIZER; // read-heavy connection hash +static pthread_mutex_t pid_cache_lock = PTHREAD_MUTEX_INITIALIZER; // PID cache only +static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER; // logged connections only typedef struct { int client_socket; @@ -135,6 +139,7 @@ static ProxyType g_proxy_type = PROXY_TYPE_SOCKS5; static char g_proxy_username[256] = ""; static char g_proxy_password[256] = ""; static bool g_dns_via_proxy = true; +static uint32_t g_proxy_ip_cached = 0; // Cached resolved proxy IP static LogCallback g_log_callback = NULL; static ConnectionCallback g_connection_callback = NULL; @@ -182,8 +187,7 @@ static bool parse_token_list(const char *list, const char *delimiters, token_mat if (list_copy == NULL) return false; - strncpy(list_copy, list, len - 1); - list_copy[len - 1] = '\0'; + memcpy(list_copy, list, len); // copy including null terminator bool matched = false; char *saveptr = NULL; char *token = strtok_r(list_copy, delimiters, &saveptr); @@ -260,7 +264,6 @@ static RuleAction check_process_rule(uint32_t src_ip, uint16_t src_port, uint32_ static void add_connection(uint16_t src_port, uint32_t src_ip, uint32_t dest_ip, uint16_t dest_port); static bool get_connection(uint16_t src_port, uint32_t *dest_ip, uint16_t *dest_port); static bool is_connection_tracked(uint16_t src_port); -static void remove_connection(uint16_t src_port); static void cleanup_stale_connections(void); static bool is_connection_already_logged(uint32_t pid, uint32_t dest_ip, uint16_t dest_port, RuleAction action); static void add_logged_connection(uint32_t pid, uint32_t dest_ip, uint16_t dest_port, RuleAction action); @@ -271,17 +274,84 @@ static void cache_pid(uint32_t src_ip, uint16_t src_port, uint32_t pid, bool is_ static void clear_pid_cache(void); static void update_has_active_rules(void); -// fast pid lookup using netlink sock_diag +// Find PID that owns a socket inode by scanning /proc/[pid]/fd/ +// Uses UID hint from netlink to skip non-matching processes +static uint32_t find_pid_from_inode(unsigned long target_inode, uint32_t uid_hint) +{ + // Build the expected link target string once + char expected[64]; + int expected_len = snprintf(expected, sizeof(expected), "socket:[%lu]", target_inode); + + DIR *proc_dir = opendir("/proc"); + if (!proc_dir) + return 0; + + uint32_t pid = 0; + struct dirent *proc_entry; + + while ((proc_entry = readdir(proc_dir)) != NULL) { + // Skip non-numeric entries (not PID dirs) + if (proc_entry->d_type != DT_DIR || !isdigit(proc_entry->d_name[0])) + continue; + + // If we have a UID hint from netlink, skip processes owned by other users + // This dramatically reduces the /proc/*/fd scan scope + if (uid_hint != (uint32_t)-1) { + struct stat proc_stat; + char proc_path[280]; + snprintf(proc_path, sizeof(proc_path), "/proc/%s", proc_entry->d_name); + if (stat(proc_path, &proc_stat) == 0 && proc_stat.st_uid != uid_hint) + continue; + } + + char fd_path[280]; + snprintf(fd_path, sizeof(fd_path), "/proc/%s/fd", proc_entry->d_name); + DIR *fd_dir = opendir(fd_path); + if (!fd_dir) + continue; + + struct dirent *fd_entry; + while ((fd_entry = readdir(fd_dir)) != NULL) { + if (fd_entry->d_name[0] == '.') + continue; + + char link_path[560]; + snprintf(link_path, sizeof(link_path), "/proc/%s/fd/%s", + proc_entry->d_name, fd_entry->d_name); + + char link_target[64]; + ssize_t link_len = readlink(link_path, link_target, sizeof(link_target) - 1); + if (link_len == expected_len) { + link_target[link_len] = '\0'; + if (memcmp(link_target, expected, expected_len) == 0) { + pid = (uint32_t)atoi(proc_entry->d_name); + closedir(fd_dir); + closedir(proc_dir); + return pid; + } + } + } + closedir(fd_dir); + } + closedir(proc_dir); + return pid; +} + +// Optimized PID lookup using netlink sock_diag +// Uses exact netlink query (no DUMP) for TCP, targeted DUMP for UDP static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_port, bool is_udp) { uint32_t cached_pid = get_cached_pid(src_ip, src_port, is_udp); if (cached_pid != 0) return cached_pid; - int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_SOCK_DIAG); - if (fd < 0) { + int fd = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_SOCK_DIAG); + if (fd < 0) return 0; - } + + // Set a short timeout to avoid blocking packet processing + struct timeval tv = {0, 100000}; // 100ms + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); struct { struct nlmsghdr nlh; @@ -291,10 +361,16 @@ static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_por memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = sizeof(req); req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY; - req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.r.sdiag_family = AF_INET; req.r.sdiag_protocol = is_udp ? IPPROTO_UDP : IPPROTO_TCP; - req.r.idiag_states = -1; // All states (UDP is connectionless, doesn't have TCP states) + + // For UDP: must use DUMP (connectionless sockets don't support exact lookup) + // For TCP: use DUMP with state filter for SYN_SENT + ESTABLISHED only + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + if (!is_udp) + req.r.idiag_states = (1 << 2) | (1 << 3); // SYN_SENT(2) + ESTABLISHED(3) only + else + req.r.idiag_states = (uint32_t)-1; // All states for UDP req.r.idiag_ext = 0; struct sockaddr_nl sa; @@ -308,171 +384,77 @@ static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_por uint32_t pid = 0; unsigned long target_inode = 0; - int matches_found = 0; - char buf[8192]; + uint32_t target_uid = (uint32_t)-1; + bool found = false; + char buf[16384]; struct iovec iov = {buf, sizeof(buf)}; struct msghdr msg = {&sa, sizeof(sa), &iov, 1, NULL, 0, 0}; while (1) { ssize_t len = recvmsg(fd, &msg, 0); - if (len < 0) { + if (len <= 0) break; - } - if (len == 0) break; struct nlmsghdr *h = (struct nlmsghdr *)buf; - while (NLMSG_OK(h, len)) { - if (h->nlmsg_type == NLMSG_DONE) { - goto done; - } - if (h->nlmsg_type == NLMSG_ERROR) { - goto done; - } + while (NLMSG_OK(h, (size_t)len)) { + if (h->nlmsg_type == NLMSG_DONE || h->nlmsg_type == NLMSG_ERROR) + goto nl_done; + if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY) { struct inet_diag_msg *r = NLMSG_DATA(h); - - // match src ip and port + + // Match src IP and port if (r->id.idiag_src[0] == src_ip && ntohs(r->id.idiag_sport) == src_port) { - matches_found++; target_inode = r->idiag_inode; - - // read /proc/net/{tcp,udp} to map inode to pid - char proc_file[64]; - snprintf(proc_file, sizeof(proc_file), "/proc/net/%s", is_udp ? "udp" : "tcp"); - FILE *fp = fopen(proc_file, "r"); - if (fp) { - char line[512]; - (void)fgets(line, sizeof(line), fp); // skip header - while (fgets(line, sizeof(line), fp)) { - unsigned long inode; - int uid_val, pid_val; - if (sscanf(line, "%*d: %*X:%*X %*X:%*X %*X %*X:%*X %*X:%*X %*X %d %*d %lu", - &uid_val, &inode) == 2) { - if (inode == target_inode) { - // now find PID from /proc/*/fd/* with this inode - // this is expensive but necessary - DIR *proc_dir = opendir("/proc"); - if (proc_dir) { - struct dirent *proc_entry; - while ((proc_entry = readdir(proc_dir)) != NULL) { - if (!isdigit(proc_entry->d_name[0])) continue; - - char fd_path[256]; - snprintf(fd_path, sizeof(fd_path), "/proc/%s/fd", proc_entry->d_name); - DIR *fd_dir = opendir(fd_path); - if (fd_dir) { - struct dirent *fd_entry; - while ((fd_entry = readdir(fd_dir)) != NULL) { - if (fd_entry->d_name[0] == '.') continue; - - char link_path[512], link_target[512]; - snprintf(link_path, sizeof(link_path), "/proc/%s/fd/%s", - proc_entry->d_name, fd_entry->d_name); - ssize_t link_len = readlink(link_path, link_target, sizeof(link_target)-1); - if (link_len > 0) { - link_target[link_len] = '\0'; - char expected[64]; - snprintf(expected, sizeof(expected), "socket:[%lu]", target_inode); - if (strcmp(link_target, expected) == 0) { - pid = atoi(proc_entry->d_name); - closedir(fd_dir); - closedir(proc_dir); - fclose(fp); - goto done; - } - } - } - closedir(fd_dir); - } - } - closedir(proc_dir); - } - fclose(fp); - goto done; - } - } - } - fclose(fp); - } - goto done; + target_uid = r->idiag_uid; // UID to narrow /proc scan + found = true; + goto nl_done; } } h = NLMSG_NEXT(h, len); } } -done: +nl_done: close(fd); - + + // If netlink found the socket, find PID from inode + // Skip the redundant /proc/net/tcp scan - netlink already gave us the inode + if (found && target_inode != 0) { + pid = find_pid_from_inode(target_inode, target_uid); + } + // Fallback for UDP: scan /proc/net/udp if netlink didn't find the socket // This happens when UDP socket uses sendto() without connect() - if (matches_found == 0 && is_udp) { - - // Try both IPv4 and IPv6 (socket might be IPv6 sending IPv4 packets) + if (!found && is_udp) { const char* udp_files[] = {"/proc/net/udp", "/proc/net/udp6"}; for (int file_idx = 0; file_idx < 2 && pid == 0; file_idx++) { FILE *fp = fopen(udp_files[file_idx], "r"); - if (fp) { - char line[512]; - (void)fgets(line, sizeof(line), fp); // skip header - int entries_scanned = 0; - while (fgets(line, sizeof(line), fp)) { - unsigned int local_addr, local_port; - unsigned long inode; - int uid_val; - - // Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode - if (sscanf(line, "%*d: %X:%X %*X:%*X %*X %*X:%*X %*X:%*X %*X %d %*d %lu", - &local_addr, &local_port, &uid_val, &inode) == 4) { - entries_scanned++; - - if (local_port == src_port) { - target_inode = inode; - - // Now find PID from /proc/*/fd/* with this inode - DIR *proc_dir = opendir("/proc"); - if (proc_dir) { - struct dirent *proc_entry; - while ((proc_entry = readdir(proc_dir)) != NULL && pid == 0) { - if (proc_entry->d_type == DT_DIR && isdigit(proc_entry->d_name[0])) { - char fd_path[256]; - snprintf(fd_path, sizeof(fd_path), "/proc/%s/fd", proc_entry->d_name); - DIR *fd_dir = opendir(fd_path); - if (fd_dir) { - struct dirent *fd_entry; - while ((fd_entry = readdir(fd_dir)) != NULL && pid == 0) { - if (fd_entry->d_type == DT_LNK) { - char link_path[512]; - snprintf(link_path, sizeof(link_path), "/proc/%s/fd/%s", - proc_entry->d_name, fd_entry->d_name); - char link_target[256]; - ssize_t link_len = readlink(link_path, link_target, sizeof(link_target) - 1); - if (link_len > 0) { - link_target[link_len] = '\0'; - char expected[64]; - snprintf(expected, sizeof(expected), "socket:[%lu]", inode); - if (strcmp(link_target, expected) == 0) { - pid = (uint32_t)atoi(proc_entry->d_name); - break; - } - } - } - } - closedir(fd_dir); - } - } - } - closedir(proc_dir); - } + if (!fp) + continue; + + char line[512]; + if (!fgets(line, sizeof(line), fp)) { // skip header + fclose(fp); + continue; + } + while (fgets(line, sizeof(line), fp)) { + unsigned int local_addr, local_port; + unsigned long inode; + int uid_val; + + if (sscanf(line, "%*d: %X:%X %*X:%*X %*X %*X:%*X %*X:%*X %*X %d %*d %lu", + &local_addr, &local_port, &uid_val, &inode) == 4) { + if (local_port == src_port && inode != 0) { + pid = find_pid_from_inode(inode, (uint32_t)uid_val); break; } } } fclose(fp); } - } } - + if (pid != 0) cache_pid(src_ip, src_port, pid, is_udp); return pid; @@ -923,12 +905,24 @@ static int http_connect(int s, uint32_t dest_ip, uint16_t dest_port) if (g_proxy_username[0] != '\0') { - char auth[512]; - snprintf(auth, sizeof(auth), "%s:%s", g_proxy_username, g_proxy_password); - - // simple base64 encode - production code should use proper base64 + // Base64 encode username:password + static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + char auth_raw[512]; + int auth_raw_len = snprintf(auth_raw, sizeof(auth_raw), "%s:%s", g_proxy_username, g_proxy_password); + char auth_b64[700]; + int j = 0; + for (int i = 0; i < auth_raw_len; i += 3) { + unsigned int n = ((unsigned char)auth_raw[i]) << 16; + if (i + 1 < auth_raw_len) n |= ((unsigned char)auth_raw[i + 1]) << 8; + if (i + 2 < auth_raw_len) n |= ((unsigned char)auth_raw[i + 2]); + auth_b64[j++] = b64[(n >> 18) & 0x3F]; + auth_b64[j++] = b64[(n >> 12) & 0x3F]; + auth_b64[j++] = (i + 1 < auth_raw_len) ? b64[(n >> 6) & 0x3F] : '='; + auth_b64[j++] = (i + 2 < auth_raw_len) ? b64[n & 0x3F] : '='; + } + auth_b64[j] = '\0'; len += snprintf(buf + len, sizeof(buf) - len, - "Proxy-Authorization: Basic %s\r\n", auth); + "Proxy-Authorization: Basic %s\r\n", auth_b64); } len += snprintf(buf + len, sizeof(buf) - len, "\r\n"); @@ -978,15 +972,21 @@ static void* connection_handler(void *arg) free(config); - // Connect to proxy - proxy_ip = resolve_hostname(g_proxy_host); + // Use cached proxy IP (resolved once at config time) + proxy_ip = g_proxy_ip_cached; if (proxy_ip == 0) { - close(client_sock); - return NULL; + // Try resolving again as fallback + proxy_ip = resolve_hostname(g_proxy_host); + if (proxy_ip == 0) + { + close(client_sock); + return NULL; + } + g_proxy_ip_cached = proxy_ip; } - proxy_sock = socket(AF_INET, SOCK_STREAM, 0); + proxy_sock = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); if (proxy_sock < 0) { close(client_sock); @@ -1046,56 +1046,49 @@ static void* connection_handler(void *arg) return NULL; } -// Windows-style transfer handler - uses select() to monitor both sockets +// Bidirectional transfer handler using poll() for efficiency static void* transfer_handler(void *arg) { transfer_config_t *config = (transfer_config_t *)arg; int sock1 = config->from_socket; // client socket int sock2 = config->to_socket; // proxy socket - char buf[131072]; // 128KB buffer - int len; + char buf[262144]; // 256KB buffer for throughput + ssize_t len; free(config); - // Monitor BOTH sockets in one thread (like Windows) + struct pollfd fds[2]; + fds[0].fd = sock1; + fds[0].events = POLLIN; + fds[1].fd = sock2; + fds[1].events = POLLIN; + while (1) { - fd_set readfds; - struct timeval timeout; - - FD_ZERO(&readfds); - FD_SET(sock1, &readfds); // client - FD_SET(sock2, &readfds); // proxy - - timeout.tv_sec = 0; - timeout.tv_usec = 50000; // 50ms - - int ready = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout); + int ready = poll(fds, 2, 30000); // 30s timeout if (ready < 0) break; if (ready == 0) - continue; + continue; // timeout, check if still running // Check client to proxy - if (FD_ISSET(sock1, &readfds)) + if (fds[0].revents & (POLLIN | POLLHUP | POLLERR)) { len = recv(sock1, buf, sizeof(buf), 0); if (len <= 0) break; - if (send_all(sock2, buf, len) < 0) break; } // Check proxy to client - if (FD_ISSET(sock2, &readfds)) + if (fds[1].revents & (POLLIN | POLLHUP | POLLERR)) { len = recv(sock2, buf, sizeof(buf), 0); if (len <= 0) break; - if (send_all(sock1, buf, len) < 0) break; } @@ -1438,7 +1431,7 @@ static void* udp_relay_server(void *arg) // Find the client that sent a packet to this destination // Iterate through hash table to find matching dest_ip:dest_port - pthread_mutex_lock(&lock); + pthread_rwlock_rdlock(&conn_lock); struct sockaddr_in client_addr; bool found_client = false; @@ -1465,7 +1458,7 @@ static void* udp_relay_server(void *arg) break; } - pthread_mutex_unlock(&lock); + pthread_rwlock_unlock(&conn_lock); if (found_client) { @@ -1555,7 +1548,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru char dest_ip_str[32]; format_ip_address(dest_ip, dest_ip_str, sizeof(dest_ip_str)); - char proxy_info[128]; + char proxy_info[300]; if (action == RULE_ACTION_PROXY) { snprintf(proxy_info, sizeof(proxy_info), "proxy %s://%s:%d tcp", @@ -1622,14 +1615,6 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru if (action == RULE_ACTION_PROXY && (dest_port == 67 || dest_port == 68)) action = RULE_ACTION_DIRECT; - // UDP proxy only works with SOCKS5, not HTTP - if (action == RULE_ACTION_PROXY && g_proxy_type != PROXY_TYPE_SOCKS5) - action = RULE_ACTION_DIRECT; - - // UDP proxy only works with SOCKS5, not HTTP - if (action == RULE_ACTION_PROXY && g_proxy_type != PROXY_TYPE_SOCKS5) - action = RULE_ACTION_DIRECT; - // UDP proxy only works with SOCKS5, not HTTP if (action == RULE_ACTION_PROXY && g_proxy_type != PROXY_TYPE_SOCKS5) action = RULE_ACTION_DIRECT; @@ -1655,7 +1640,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru char dest_ip_str[32]; format_ip_address(dest_ip, dest_ip_str, sizeof(dest_ip_str)); - char proxy_info[128]; + char proxy_info[300]; if (action == RULE_ACTION_PROXY) { snprintf(proxy_info, sizeof(proxy_info), "proxy socks5://%s:%d udp", @@ -1723,7 +1708,7 @@ static inline uint32_t connection_hash(uint16_t port) static void add_connection(uint16_t src_port, uint32_t src_ip, uint32_t dest_ip, uint16_t dest_port) { uint32_t hash = connection_hash(src_port); - pthread_mutex_lock(&lock); + pthread_rwlock_wrlock(&conn_lock); CONNECTION_INFO *conn = connection_hash_table[hash]; while (conn != NULL) @@ -1735,7 +1720,7 @@ static void add_connection(uint16_t src_port, uint32_t src_ip, uint32_t dest_ip, conn->src_ip = src_ip; conn->is_tracked = true; conn->last_activity = get_monotonic_ms(); - pthread_mutex_unlock(&lock); + pthread_rwlock_unlock(&conn_lock); return; } conn = conn->next; @@ -1754,13 +1739,13 @@ static void add_connection(uint16_t src_port, uint32_t src_ip, uint32_t dest_ip, connection_hash_table[hash] = new_conn; } - pthread_mutex_unlock(&lock); + pthread_rwlock_unlock(&conn_lock); } static bool get_connection(uint16_t src_port, uint32_t *dest_ip, uint16_t *dest_port) { uint32_t hash = connection_hash(src_port); - pthread_mutex_lock(&lock); + pthread_rwlock_rdlock(&conn_lock); CONNECTION_INFO *conn = connection_hash_table[hash]; while (conn != NULL) @@ -1769,41 +1754,41 @@ static bool get_connection(uint16_t src_port, uint32_t *dest_ip, uint16_t *dest_ { *dest_ip = conn->orig_dest_ip; *dest_port = conn->orig_dest_port; - conn->last_activity = get_monotonic_ms(); - pthread_mutex_unlock(&lock); + conn->last_activity = get_monotonic_ms(); // benign race on timestamp + pthread_rwlock_unlock(&conn_lock); return true; } conn = conn->next; } - pthread_mutex_unlock(&lock); + pthread_rwlock_unlock(&conn_lock); return false; } static bool is_connection_tracked(uint16_t src_port) { uint32_t hash = connection_hash(src_port); - pthread_mutex_lock(&lock); + pthread_rwlock_rdlock(&conn_lock); CONNECTION_INFO *conn = connection_hash_table[hash]; while (conn != NULL) { if (conn->src_port == src_port && conn->is_tracked) { - pthread_mutex_unlock(&lock); + pthread_rwlock_unlock(&conn_lock); return true; } conn = conn->next; } - pthread_mutex_unlock(&lock); + pthread_rwlock_unlock(&conn_lock); return false; } -static void remove_connection(uint16_t src_port) +static void __attribute__((unused)) remove_connection(uint16_t src_port) { uint32_t hash = connection_hash(src_port); - pthread_mutex_lock(&lock); + pthread_rwlock_wrlock(&conn_lock); CONNECTION_INFO **conn_ptr = &connection_hash_table[hash]; while (*conn_ptr != NULL) @@ -1813,51 +1798,46 @@ static void remove_connection(uint16_t src_port) CONNECTION_INFO *to_free = *conn_ptr; *conn_ptr = (*conn_ptr)->next; free(to_free); - pthread_mutex_unlock(&lock); + pthread_rwlock_unlock(&conn_lock); return; } conn_ptr = &(*conn_ptr)->next; } - pthread_mutex_unlock(&lock); + pthread_rwlock_unlock(&conn_lock); } static void cleanup_stale_connections(void) { uint64_t now = get_monotonic_ms(); - int removed = 0; - // Process each hash bucket with minimal lock time + // Cleanup connection hash table for (int i = 0; i < CONNECTION_HASH_SIZE; i++) { - pthread_mutex_lock(&lock); + pthread_rwlock_wrlock(&conn_lock); CONNECTION_INFO **conn_ptr = &connection_hash_table[i]; while (*conn_ptr != NULL) { - if (now - (*conn_ptr)->last_activity > 60000) // 60 sec timeout (same as Windows) + if (now - (*conn_ptr)->last_activity > 60000) // 60 sec timeout { CONNECTION_INFO *to_free = *conn_ptr; *conn_ptr = (*conn_ptr)->next; - pthread_mutex_unlock(&lock); - free(to_free); // Free outside lock - removed++; - pthread_mutex_lock(&lock); + free(to_free); } else { conn_ptr = &(*conn_ptr)->next; } } - pthread_mutex_unlock(&lock); + pthread_rwlock_unlock(&conn_lock); } - // Cleanup PID cache + // Cleanup PID cache (separate lock - no contention with connection lookups) uint64_t now_cache = get_monotonic_ms(); - int cache_removed = 0; for (int i = 0; i < PID_CACHE_SIZE; i++) { - pthread_mutex_lock(&lock); + pthread_mutex_lock(&pid_cache_lock); PID_CACHE_ENTRY **entry_ptr = &pid_cache[i]; while (*entry_ptr != NULL) { @@ -1865,21 +1845,18 @@ static void cleanup_stale_connections(void) { PID_CACHE_ENTRY *to_free = *entry_ptr; *entry_ptr = (*entry_ptr)->next; - pthread_mutex_unlock(&lock); - free(to_free); // Free outside lock - cache_removed++; - pthread_mutex_lock(&lock); + free(to_free); } else { entry_ptr = &(*entry_ptr)->next; } } - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&pid_cache_lock); } - // Keep only last 100 logged connections for memory efficiency - pthread_mutex_lock(&lock); + // Keep only last 100 logged connections + pthread_mutex_lock(&log_lock); int logged_count = 0; LOGGED_CONNECTION *temp = logged_connections; while (temp != NULL) @@ -1907,12 +1884,12 @@ static void cleanup_stale_connections(void) } } } - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&log_lock); } static bool is_connection_already_logged(uint32_t pid, uint32_t dest_ip, uint16_t dest_port, RuleAction action) { - pthread_mutex_lock(&lock); + pthread_mutex_lock(&log_lock); LOGGED_CONNECTION *logged = logged_connections; while (logged != NULL) @@ -1920,19 +1897,19 @@ static bool is_connection_already_logged(uint32_t pid, uint32_t dest_ip, uint16_ if (logged->pid == pid && logged->dest_ip == dest_ip && logged->dest_port == dest_port && logged->action == action) { - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&log_lock); return true; } logged = logged->next; } - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&log_lock); return false; } static void add_logged_connection(uint32_t pid, uint32_t dest_ip, uint16_t dest_port, RuleAction action) { - pthread_mutex_lock(&lock); + pthread_mutex_lock(&log_lock); // keep only last 100 entries to avoid memory growth int count = 0; @@ -1956,14 +1933,13 @@ static void add_logged_connection(uint32_t pid, uint32_t dest_ip, uint16_t dest_ LOGGED_CONNECTION *to_free_list = temp->next; temp->next = NULL; - pthread_mutex_unlock(&lock); + // Free excess entries (still under log_lock, but this is rare) while (to_free_list != NULL) { LOGGED_CONNECTION *next = to_free_list->next; free(to_free_list); to_free_list = next; } - pthread_mutex_lock(&lock); } } @@ -1978,12 +1954,12 @@ static void add_logged_connection(uint32_t pid, uint32_t dest_ip, uint16_t dest_ logged_connections = logged; } - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&log_lock); } static void clear_logged_connections(void) { - pthread_mutex_lock(&lock); + pthread_mutex_lock(&log_lock); while (logged_connections != NULL) { @@ -1992,7 +1968,7 @@ static void clear_logged_connections(void) free(to_free); } - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&log_lock); } static uint32_t pid_cache_hash(uint32_t src_ip, uint16_t src_port, bool is_udp) @@ -2007,7 +1983,7 @@ static uint32_t get_cached_pid(uint32_t src_ip, uint16_t src_port, bool is_udp) uint64_t current_time = get_monotonic_ms(); uint32_t pid = 0; - pthread_mutex_lock(&lock); + pthread_mutex_lock(&pid_cache_lock); PID_CACHE_ENTRY *entry = pid_cache[hash]; while (entry != NULL) @@ -2029,7 +2005,7 @@ static uint32_t get_cached_pid(uint32_t src_ip, uint16_t src_port, bool is_udp) entry = entry->next; } - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&pid_cache_lock); return pid; } @@ -2038,7 +2014,7 @@ static void cache_pid(uint32_t src_ip, uint16_t src_port, uint32_t pid, bool is_ uint32_t hash = pid_cache_hash(src_ip, src_port, is_udp); uint64_t current_time = get_monotonic_ms(); - pthread_mutex_lock(&lock); + pthread_mutex_lock(&pid_cache_lock); PID_CACHE_ENTRY *entry = pid_cache[hash]; while (entry != NULL) @@ -2049,7 +2025,7 @@ static void cache_pid(uint32_t src_ip, uint16_t src_port, uint32_t pid, bool is_ { entry->pid = pid; entry->timestamp = current_time; - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&pid_cache_lock); return; } entry = entry->next; @@ -2067,12 +2043,12 @@ static void cache_pid(uint32_t src_ip, uint16_t src_port, uint32_t pid, bool is_ pid_cache[hash] = new_entry; } - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&pid_cache_lock); } static void clear_pid_cache(void) { - pthread_mutex_lock(&lock); + pthread_mutex_lock(&pid_cache_lock); for (int i = 0; i < PID_CACHE_SIZE; i++) { @@ -2084,7 +2060,7 @@ static void clear_pid_cache(void) } } - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&pid_cache_lock); } static void* cleanup_worker(void *arg) @@ -2302,7 +2278,8 @@ bool ProxyBridge_SetProxyConfig(ProxyType type, const char* proxy_ip, uint16_t p if (proxy_ip == NULL || proxy_ip[0] == '\0' || proxy_port == 0) return false; - if (resolve_hostname(proxy_ip) == 0) + g_proxy_ip_cached = resolve_hostname(proxy_ip); + if (g_proxy_ip_cached == 0) return false; strncpy(g_proxy_host, proxy_ip, sizeof(g_proxy_host) - 1); @@ -2531,7 +2508,7 @@ bool ProxyBridge_Stop(void) } // Free all connections in hash table - pthread_mutex_lock(&lock); + pthread_rwlock_wrlock(&conn_lock); for (int i = 0; i < CONNECTION_HASH_SIZE; i++) { while (connection_hash_table[i] != NULL) @@ -2541,7 +2518,7 @@ bool ProxyBridge_Stop(void) free(to_free); } } - pthread_mutex_unlock(&lock); + pthread_rwlock_unlock(&conn_lock); clear_logged_connections(); clear_pid_cache(); @@ -2554,7 +2531,6 @@ int ProxyBridge_TestConnection(const char* target_host, uint16_t target_port, ch { int test_sock = -1; struct sockaddr_in proxy_addr; - struct hostent* host_info; uint32_t target_ip; int ret = -1; char temp_buffer[512]; @@ -2578,14 +2554,20 @@ int ProxyBridge_TestConnection(const char* target_host, uint16_t target_port, ch strncpy(result_buffer, temp_buffer, buffer_size - 1); result_buffer[buffer_size - 1] = '\0'; - host_info = gethostbyname(target_host); - if (host_info == NULL) + struct addrinfo hints, *res; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + int gai_ret = getaddrinfo(target_host, NULL, &hints, &res); + if (gai_ret != 0) { - snprintf(temp_buffer, sizeof(temp_buffer), "error failed to resolve hostname %s\n", target_host); + snprintf(temp_buffer, sizeof(temp_buffer), "error failed to resolve hostname %s: %s\n", + target_host, gai_strerror(gai_ret)); strncat(result_buffer, temp_buffer, buffer_size - strlen(result_buffer) - 1); return -1; } - target_ip = *(uint32_t*)host_info->h_addr_list[0]; + target_ip = ((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr; + freeaddrinfo(res); snprintf(temp_buffer, sizeof(temp_buffer), "resolved %s to %d.%d.%d.%d\n", target_host, @@ -2721,9 +2703,9 @@ static void library_cleanup(void) { // Even if not running, ensure iptables rules are removed // This handles cases where the app crashed before calling Stop - system("iptables -t mangle -D OUTPUT -p tcp -j NFQUEUE --queue-num 0 2>/dev/null"); - system("iptables -t mangle -D OUTPUT -p udp -j NFQUEUE --queue-num 0 2>/dev/null"); - system("iptables -t nat -D OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010 2>/dev/null"); - system("iptables -t nat -D OUTPUT -p udp -m mark --mark 2 -j REDIRECT --to-port 34011 2>/dev/null"); + (void)!system("iptables -t mangle -D OUTPUT -p tcp -j NFQUEUE --queue-num 0 2>/dev/null"); + (void)!system("iptables -t mangle -D OUTPUT -p udp -j NFQUEUE --queue-num 0 2>/dev/null"); + (void)!system("iptables -t nat -D OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010 2>/dev/null"); + (void)!system("iptables -t nat -D OUTPUT -p udp -m mark --mark 2 -j REDIRECT --to-port 34011 2>/dev/null"); } } From 5ed2cf1ec9b479e2855cc23cc18440453ae77415 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 14:57:12 +0530 Subject: [PATCH 12/58] improved socket --- Linux/src/ProxyBridge.c | 240 +++++++++++++++++++++++++++++++++------- 1 file changed, 197 insertions(+), 43 deletions(-) diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index 5887c4f..8b668a4 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -229,7 +229,7 @@ static ssize_t send_all(int sock, const char *buf, size_t len) { size_t sent = 0; while (sent < len) { - ssize_t n = send(sock, buf + sent, len - sent, 0); + ssize_t n = send(sock, buf + sent, len - sent, MSG_NOSIGNAL); if (n < 0) return -1; sent += n; } @@ -993,8 +993,8 @@ static void* connection_handler(void *arg) return NULL; } - configure_tcp_socket(proxy_sock, 524288, 30000); - configure_tcp_socket(client_sock, 524288, 30000); + configure_tcp_socket(proxy_sock, 1048576, 60000); + configure_tcp_socket(client_sock, 1048576, 60000); memset(&proxy_addr, 0, sizeof(proxy_addr)); proxy_addr.sin_family = AF_INET; @@ -1046,55 +1046,194 @@ static void* connection_handler(void *arg) return NULL; } -// Bidirectional transfer handler using poll() for efficiency +// High-performance bidirectional relay using splice() for zero-copy transfer. +// Data moves kernel→kernel through a pipe, never touching userspace memory. +// This eliminates the main throughput bottleneck of copying data through userspace. static void* transfer_handler(void *arg) { transfer_config_t *config = (transfer_config_t *)arg; int sock1 = config->from_socket; // client socket int sock2 = config->to_socket; // proxy socket - char buf[262144]; // 256KB buffer for throughput - ssize_t len; - free(config); - struct pollfd fds[2]; - fds[0].fd = sock1; - fds[0].events = POLLIN; - fds[1].fd = sock2; - fds[1].events = POLLIN; + // Create pipes for splice: one per direction + // pipe_a: sock2 (proxy) → sock1 (client) = DOWNLOAD + // pipe_b: sock1 (client) → sock2 (proxy) = UPLOAD + int pipe_a[2] = {-1, -1}; + int pipe_b[2] = {-1, -1}; - while (1) - { - int ready = poll(fds, 2, 30000); // 30s timeout + if (pipe2(pipe_a, O_CLOEXEC | O_NONBLOCK) < 0 || + pipe2(pipe_b, O_CLOEXEC | O_NONBLOCK) < 0) { + // pipe creation failed - close any that opened and fall back + if (pipe_a[0] >= 0) { close(pipe_a[0]); close(pipe_a[1]); } + if (pipe_b[0] >= 0) { close(pipe_b[0]); close(pipe_b[1]); } + goto fallback; + } - if (ready < 0) - break; + // Increase pipe capacity for throughput (default 64KB → 1MB) + fcntl(pipe_a[0], F_SETPIPE_SZ, 1048576); + fcntl(pipe_b[0], F_SETPIPE_SZ, 1048576); - if (ready == 0) - continue; // timeout, check if still running + // Make sockets non-blocking for splice + fcntl(sock1, F_SETFL, fcntl(sock1, F_GETFL, 0) | O_NONBLOCK); + fcntl(sock2, F_SETFL, fcntl(sock2, F_GETFL, 0) | O_NONBLOCK); - // Check client to proxy - if (fds[0].revents & (POLLIN | POLLHUP | POLLERR)) + { + struct pollfd fds[2]; + ssize_t pipe_a_bytes = 0; // bytes in download pipe (proxy→client) + ssize_t pipe_b_bytes = 0; // bytes in upload pipe (client→proxy) + bool sock1_done = false; // client EOF or error + bool sock2_done = false; // proxy EOF or error + bool shut_wr_sock1 = false; // already called shutdown(sock1, SHUT_WR) + bool shut_wr_sock2 = false; // already called shutdown(sock2, SHUT_WR) + + while (1) { - len = recv(sock1, buf, sizeof(buf), 0); - if (len <= 0) + // Build poll set - use fd=-1 to exclude sockets we're done with + // CRITICAL: poll() always reports POLLHUP even if events=0, + // causing a busy-loop. Setting fd=-1 makes poll() skip the entry entirely. + fds[0].fd = (!sock1_done || pipe_a_bytes > 0 || pipe_b_bytes > 0) ? sock1 : -1; + fds[1].fd = (!sock2_done || pipe_b_bytes > 0 || pipe_a_bytes > 0) ? sock2 : -1; + fds[0].events = 0; + fds[1].events = 0; + fds[0].revents = 0; + fds[1].revents = 0; + + // Download direction: proxy→pipe_a→client + if (!sock2_done && pipe_a_bytes == 0) + fds[1].events |= POLLIN; // read from proxy + if (pipe_a_bytes > 0) + fds[0].events |= POLLOUT; // write to client + + // Upload direction: client→pipe_b→proxy + if (!sock1_done && pipe_b_bytes == 0) + fds[0].events |= POLLIN; // read from client + if (pipe_b_bytes > 0) + fds[1].events |= POLLOUT; // write to proxy + + // Nothing left to do + if (fds[0].fd == -1 && fds[1].fd == -1) break; - if (send_all(sock2, buf, len) < 0) + if (fds[0].events == 0 && fds[1].events == 0) break; - } - // Check proxy to client - if (fds[1].revents & (POLLIN | POLLHUP | POLLERR)) - { - len = recv(sock2, buf, sizeof(buf), 0); - if (len <= 0) + int ready = poll(fds, 2, 60000); + if (ready < 0) { + if (errno == EINTR) continue; break; - if (send_all(sock1, buf, len) < 0) + } + if (ready == 0) + break; // 60s idle timeout + + // Handle errors and hangups on each socket + if (fds[0].revents & POLLERR) break; + if (fds[1].revents & POLLERR) break; + + // POLLHUP means peer closed - treat as EOF for reads + if ((fds[1].revents & POLLHUP) && !(fds[1].revents & POLLIN)) + sock2_done = true; + if ((fds[0].revents & POLLHUP) && !(fds[0].revents & POLLIN)) + sock1_done = true; + + // === DOWNLOAD: proxy (sock2) → pipe_a → client (sock1) === + + if (!sock2_done && pipe_a_bytes == 0 && (fds[1].revents & POLLIN)) { + ssize_t n = splice(sock2, NULL, pipe_a[1], NULL, 1048576, + SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + if (n > 0) { + pipe_a_bytes = n; + } else if (n == 0) { + sock2_done = true; + } else if (errno != EAGAIN && errno != EWOULDBLOCK) { + sock2_done = true; + } + } + + if (pipe_a_bytes > 0 && (fds[0].revents & POLLOUT)) { + ssize_t n = splice(pipe_a[0], NULL, sock1, NULL, pipe_a_bytes, + SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + if (n > 0) { + pipe_a_bytes -= n; + } else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + break; + } + } + + // === UPLOAD: client (sock1) → pipe_b → proxy (sock2) === + + if (!sock1_done && pipe_b_bytes == 0 && (fds[0].revents & POLLIN)) { + ssize_t n = splice(sock1, NULL, pipe_b[1], NULL, 1048576, + SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + if (n > 0) { + pipe_b_bytes = n; + } else if (n == 0) { + sock1_done = true; + } else if (errno != EAGAIN && errno != EWOULDBLOCK) { + sock1_done = true; + } + } + + if (pipe_b_bytes > 0 && (fds[1].revents & POLLOUT)) { + ssize_t n = splice(pipe_b[0], NULL, sock2, NULL, pipe_b_bytes, + SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + if (n > 0) { + pipe_b_bytes -= n; + } else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + break; + } + } + + // Half-close: when one side is done and its pipe is drained, + // signal the other side with shutdown(SHUT_WR) + if (sock2_done && pipe_a_bytes == 0 && !shut_wr_sock1) { + shutdown(sock1, SHUT_WR); + shut_wr_sock1 = true; + } + if (sock1_done && pipe_b_bytes == 0 && !shut_wr_sock2) { + shutdown(sock2, SHUT_WR); + shut_wr_sock2 = true; + } + + // Both sides done and all pipes drained + if (sock1_done && sock2_done && pipe_a_bytes == 0 && pipe_b_bytes == 0) break; } } - // Cleanup + close(pipe_a[0]); close(pipe_a[1]); + close(pipe_b[0]); close(pipe_b[1]); + goto cleanup; + +fallback: + // Fallback: traditional recv/send relay if pipes failed + { + char buf[131072]; + struct pollfd fds[2]; + fds[0].fd = sock1; + fds[0].events = POLLIN; + fds[1].fd = sock2; + fds[1].events = POLLIN; + + while (1) { + int ready = poll(fds, 2, 60000); + if (ready <= 0) break; + + if (fds[0].revents & (POLLERR | POLLHUP | POLLIN)) { + if (fds[0].revents & POLLERR) break; + ssize_t n = recv(sock1, buf, sizeof(buf), MSG_NOSIGNAL); + if (n <= 0) break; + if (send_all(sock2, buf, n) < 0) break; + } + if (fds[1].revents & (POLLERR | POLLHUP | POLLIN)) { + if (fds[1].revents & POLLERR) break; + ssize_t n = recv(sock2, buf, sizeof(buf), MSG_NOSIGNAL); + if (n <= 0) break; + if (send_all(sock1, buf, n) < 0) break; + } + } + } + +cleanup: shutdown(sock1, SHUT_RDWR); shutdown(sock2, SHUT_RDWR); close(sock1); @@ -1102,7 +1241,7 @@ static void* transfer_handler(void *arg) return NULL; } -// Windows-style proxy server - accepts and spawns thread per connection +// Proxy server - accepts connections and spawns relay threads static void* local_proxy_server(void *arg) { (void)arg; @@ -1110,7 +1249,7 @@ static void* local_proxy_server(void *arg) int listen_sock; int on = 1; - listen_sock = socket(AF_INET, SOCK_STREAM, 0); + listen_sock = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); if (listen_sock < 0) { log_message("Socket creation failed"); @@ -1139,19 +1278,26 @@ static void* local_proxy_server(void *arg) return NULL; } + // Pre-create thread attributes with small stack (256KB instead of 8MB default) + // Each relay thread only needs splice() state - no 256KB userspace buffer anymore + pthread_attr_t thread_attr; + pthread_attr_init(&thread_attr); + pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setstacksize(&thread_attr, 262144); // 256KB stack + + struct pollfd pfd; + pfd.fd = listen_sock; + pfd.events = POLLIN; + while (running) { - fd_set read_fds; - FD_ZERO(&read_fds); - FD_SET(listen_sock, &read_fds); - struct timeval timeout = {1, 0}; - - if (select(FD_SETSIZE, &read_fds, NULL, NULL, &timeout) <= 0) + int ready = poll(&pfd, 1, 1000); // 1s timeout + if (ready <= 0) continue; struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); - int client_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &addr_len); + int client_sock = accept4(listen_sock, (struct sockaddr *)&client_addr, &addr_len, SOCK_CLOEXEC); if (client_sock < 0) continue; @@ -1174,15 +1320,15 @@ static void* local_proxy_server(void *arg) } pthread_t conn_thread; - if (pthread_create(&conn_thread, NULL, connection_handler, (void*)conn_config) != 0) + if (pthread_create(&conn_thread, &thread_attr, connection_handler, (void*)conn_config) != 0) { close(client_sock); free(conn_config); continue; } - pthread_detach(conn_thread); } + pthread_attr_destroy(&thread_attr); close(listen_sock); return NULL; } @@ -2345,6 +2491,14 @@ bool ProxyBridge_Start(void) running = true; g_current_process_id = getpid(); + // Raise system socket buffer limits for high throughput (requires root) + // Default rmem_max/wmem_max is usually 208KB, far too small for >100Mbps + FILE *fp; + fp = fopen("/proc/sys/net/core/rmem_max", "w"); + if (fp) { fprintf(fp, "4194304"); fclose(fp); } // 4MB + fp = fopen("/proc/sys/net/core/wmem_max", "w"); + if (fp) { fprintf(fp, "4194304"); fclose(fp); } // 4MB + if (pthread_create(&proxy_thread, NULL, local_proxy_server, NULL) != 0) { running = false; From de79a7e8f45bdf1f3dc6790f063f5aeb3d53ec38 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 16:46:23 +0530 Subject: [PATCH 13/58] fixed connection and udp socsk5 error, connection failed to reconnect, fixed linux and windows any ip udp associate --- Linux/src/ProxyBridge.c | 255 +++++++++++++++++++++++++++----------- Windows/src/ProxyBridge.c | 4 + 2 files changed, 186 insertions(+), 73 deletions(-) diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index 8b668a4..c3bfc74 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -64,17 +65,6 @@ typedef struct CONNECTION_INFO { struct CONNECTION_INFO *next; } CONNECTION_INFO; -typedef struct { - int client_socket; - uint32_t orig_dest_ip; - uint16_t orig_dest_port; -} CONNECTION_CONFIG; - -typedef struct { - int from_socket; - int to_socket; -} TRANSFER_CONFIG; - typedef struct LOGGED_CONNECTION { uint32_t pid; uint32_t dest_ip; @@ -97,6 +87,7 @@ static LOGGED_CONNECTION *logged_connections = NULL; static PROCESS_RULE *rules_list = NULL; static uint32_t g_next_rule_id = 1; static pthread_rwlock_t conn_lock = PTHREAD_RWLOCK_INITIALIZER; // read-heavy connection hash +static pthread_rwlock_t rules_lock = PTHREAD_RWLOCK_INITIALIZER; // read-heavy rules list static pthread_mutex_t pid_cache_lock = PTHREAD_MUTEX_INITIALIZER; // PID cache only static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER; // logged connections only @@ -788,7 +779,9 @@ static RuleAction check_process_rule(uint32_t src_ip, uint16_t src_port, uint32_ if (!get_process_name_from_pid(pid, process_name, sizeof(process_name))) return RULE_ACTION_DIRECT; + pthread_rwlock_rdlock(&rules_lock); RuleAction action = match_rule(process_name, dest_ip, dest_port, is_udp); + pthread_rwlock_unlock(&rules_lock); if (action == RULE_ACTION_PROXY && is_udp && g_proxy_type == PROXY_TYPE_HTTP) { @@ -814,7 +807,7 @@ static int socks5_connect(int s, uint32_t dest_ip, uint16_t dest_port) buf[1] = 0x02; buf[2] = SOCKS5_AUTH_NONE; buf[3] = 0x02; - if (send(s, buf, 4, 0) != 4) + if (send(s, buf, 4, MSG_NOSIGNAL) != 4) { log_message("socks5 failed to send auth methods"); return -1; @@ -824,7 +817,7 @@ static int socks5_connect(int s, uint32_t dest_ip, uint16_t dest_port) { buf[1] = 0x01; buf[2] = SOCKS5_AUTH_NONE; - if (send(s, buf, 3, 0) != 3) + if (send(s, buf, 3, MSG_NOSIGNAL) != 3) { log_message("socks5 failed to send auth methods"); return -1; @@ -848,7 +841,7 @@ static int socks5_connect(int s, uint32_t dest_ip, uint16_t dest_port) buf[2 + ulen] = (unsigned char)plen; memcpy(buf + 3 + ulen, g_proxy_password, plen); - if (send(s, buf, 3 + ulen + plen, 0) != (ssize_t)(3 + ulen + plen)) + if (send(s, buf, 3 + ulen + plen, MSG_NOSIGNAL) != (ssize_t)(3 + ulen + plen)) { log_message("socks5 failed to send credentials"); return -1; @@ -875,7 +868,7 @@ static int socks5_connect(int s, uint32_t dest_ip, uint16_t dest_port) uint16_t port_net = htons(dest_port); memcpy(buf + 8, &port_net, 2); - if (send(s, buf, 10, 0) != 10) + if (send(s, buf, 10, MSG_NOSIGNAL) != 10) { log_message("socks5 failed to send connect request"); return -1; @@ -1046,8 +1039,8 @@ static void* connection_handler(void *arg) return NULL; } -// High-performance bidirectional relay using splice() for zero-copy transfer. -// Data moves kernel→kernel through a pipe, never touching userspace memory. +// bidirectional relay using splice() for zero-copy transfer. +// data moves from kernel to kernel through a pipe, never touching userspace memory. // This eliminates the main throughput bottleneck of copying data through userspace. static void* transfer_handler(void *arg) { @@ -1218,18 +1211,24 @@ static void* transfer_handler(void *arg) int ready = poll(fds, 2, 60000); if (ready <= 0) break; - if (fds[0].revents & (POLLERR | POLLHUP | POLLIN)) { - if (fds[0].revents & POLLERR) break; + if (fds[0].revents & POLLERR || fds[1].revents & POLLERR) break; + + bool did_work = false; + if (fds[0].revents & POLLIN) { ssize_t n = recv(sock1, buf, sizeof(buf), MSG_NOSIGNAL); if (n <= 0) break; if (send_all(sock2, buf, n) < 0) break; + did_work = true; } - if (fds[1].revents & (POLLERR | POLLHUP | POLLIN)) { - if (fds[1].revents & POLLERR) break; + if (fds[1].revents & POLLIN) { ssize_t n = recv(sock2, buf, sizeof(buf), MSG_NOSIGNAL); if (n <= 0) break; if (send_all(sock1, buf, n) < 0) break; + did_work = true; } + + // POLLHUP with no POLLIN data means peer closed - exit + if (!did_work) break; } } @@ -1347,7 +1346,7 @@ static int socks5_udp_associate(int s, struct sockaddr_in *relay_addr) if (use_auth) buf[3] = 0x02; // username/password auth - if (send(s, buf, use_auth ? 4 : 3, 0) != (use_auth ? 4 : 3)) + if (send(s, buf, use_auth ? 4 : 3, MSG_NOSIGNAL) != (use_auth ? 4 : 3)) return -1; len = recv(s, buf, 2, 0); @@ -1364,7 +1363,7 @@ static int socks5_udp_associate(int s, struct sockaddr_in *relay_addr) buf[2 + ulen] = (unsigned char)plen; memcpy(buf + 3 + ulen, g_proxy_password, plen); - if (send(s, buf, 3 + ulen + plen, 0) != (ssize_t)(3 + ulen + plen)) + if (send(s, buf, 3 + ulen + plen, MSG_NOSIGNAL) != (ssize_t)(3 + ulen + plen)) return -1; len = recv(s, buf, 2, 0); @@ -1382,7 +1381,7 @@ static int socks5_udp_associate(int s, struct sockaddr_in *relay_addr) memset(buf + 4, 0, 4); // 0.0.0.0 memset(buf + 8, 0, 2); // port 0 - if (send(s, buf, 10, 0) != 10) + if (send(s, buf, 10, MSG_NOSIGNAL) != 10) return -1; len = recv(s, buf, 512, 0); @@ -1452,6 +1451,10 @@ static bool establish_udp_associate(void) return false; } + // RFC 1928: if server returns 0.0.0.0 as relay address, use the proxy server's IP + if (socks5_udp_relay_addr.sin_addr.s_addr == INADDR_ANY) + socks5_udp_relay_addr.sin_addr.s_addr = socks5_ip; + socks5_udp_control_socket = tcp_sock; socks5_udp_send_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); @@ -1465,9 +1468,26 @@ static bool establish_udp_associate(void) configure_udp_socket(socks5_udp_send_socket, 262144, 30000); udp_associate_connected = true; + log_message("UDP ASSOCIATE established with SOCKS5 proxy"); return true; } +// Tear down UDP associate state so next packet triggers reconnect +static void teardown_udp_associate(void) +{ + udp_associate_connected = false; + if (socks5_udp_control_socket >= 0) + { + close(socks5_udp_control_socket); + socks5_udp_control_socket = -1; + } + if (socks5_udp_send_socket >= 0) + { + close(socks5_udp_send_socket); + socks5_udp_send_socket = -1; + } +} + static void* udp_relay_server(void *arg) { (void)arg; @@ -1496,30 +1516,50 @@ static void* udp_relay_server(void *arg) return NULL; } + // Try initial connection (non-fatal if proxy not running yet) udp_associate_connected = establish_udp_associate(); while (running) { - fd_set read_fds; - FD_ZERO(&read_fds); - FD_SET(udp_relay_socket, &read_fds); + struct pollfd fds[3]; + int nfds = 1; - int max_fd = udp_relay_socket; + // Always monitor the local relay socket for client packets + fds[0].fd = udp_relay_socket; + fds[0].events = POLLIN; + fds[0].revents = 0; - if (udp_associate_connected && socks5_udp_send_socket >= 0) - { - FD_SET(socks5_udp_send_socket, &read_fds); - if (socks5_udp_send_socket > max_fd) - max_fd = socks5_udp_send_socket; - } + // Monitor SOCKS5 UDP socket for proxy responses + fds[1].fd = (udp_associate_connected && socks5_udp_send_socket >= 0) ? socks5_udp_send_socket : -1; + fds[1].events = POLLIN; + fds[1].revents = 0; - struct timeval timeout = {1, 0}; + // Monitor SOCKS5 TCP control socket for death detection (like Windows MSG_PEEK) + fds[2].fd = (udp_associate_connected && socks5_udp_control_socket >= 0) ? socks5_udp_control_socket : -1; + fds[2].events = POLLIN; + fds[2].revents = 0; + nfds = 3; - if (select(max_fd + 1, &read_fds, NULL, NULL, &timeout) <= 0) + int ready = poll(fds, nfds, 1000); // 1s timeout + if (ready <= 0) continue; + // Check TCP control socket health (Windows-style MSG_PEEK probe) + // If the TCP control connection dies, the UDP associate is invalid + if (fds[2].fd >= 0 && (fds[2].revents & (POLLIN | POLLHUP | POLLERR))) + { + char peek_buf[1]; + ssize_t peek_len = recv(socks5_udp_control_socket, peek_buf, 1, MSG_PEEK | MSG_DONTWAIT); + if (peek_len == 0 || (peek_len < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) + { + // TCP control connection died - proxy is gone + teardown_udp_associate(); + continue; + } + } + // Client -> SOCKS5 proxy - if (FD_ISSET(udp_relay_socket, &read_fds)) + if (fds[0].revents & POLLIN) { from_len = sizeof(from_addr); ssize_t recv_len = recvfrom(udp_relay_socket, recv_buf, sizeof(recv_buf), 0, @@ -1527,6 +1567,7 @@ static void* udp_relay_server(void *arg) if (recv_len <= 0) continue; + // Try to establish connection if not connected (proxy may have started) if (!udp_associate_connected) { if (!establish_udp_associate()) @@ -1540,6 +1581,10 @@ static void* udp_relay_server(void *arg) if (!get_connection(client_port, &dest_ip, &dest_port)) continue; + // Buffer overflow guard: ensure data + 10-byte SOCKS5 header fits + if (recv_len > (ssize_t)(sizeof(send_buf) - 10)) + continue; + // Build SOCKS5 UDP packet: +----+------+------+----------+----------+----------+ // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | // +----+------+------+----------+----------+----------+ @@ -1552,19 +1597,37 @@ static void* udp_relay_server(void *arg) memcpy(send_buf + 8, &port_net, 2); memcpy(send_buf + 10, recv_buf, recv_len); - sendto(socks5_udp_send_socket, send_buf, 10 + recv_len, 0, + ssize_t sent = sendto(socks5_udp_send_socket, send_buf, 10 + recv_len, 0, (struct sockaddr *)&socks5_udp_relay_addr, sizeof(socks5_udp_relay_addr)); + + // If sendto fails, proxy is dead - tear down and retry on next packet + if (sent < 0) + { + teardown_udp_associate(); + } } // SOCKS5 proxy -> Client - if (udp_associate_connected && socks5_udp_send_socket >= 0 && FD_ISSET(socks5_udp_send_socket, &read_fds)) + if (fds[1].fd >= 0 && (fds[1].revents & POLLIN)) { from_len = sizeof(from_addr); ssize_t recv_len = recvfrom(socks5_udp_send_socket, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&from_addr, &from_len); + + // Handle socket errors - proxy may have died + if (recv_len < 0) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + teardown_udp_associate(); + continue; + } if (recv_len < 10) continue; + // Fragmented SOCKS5 UDP packets not supported + if (recv_buf[2] != 0x00) + continue; + // Parse SOCKS5 UDP packet if (recv_buf[3] != SOCKS5_ATYP_IPV4) continue; @@ -1614,12 +1677,15 @@ static void* udp_relay_server(void *arg) (struct sockaddr *)&client_addr, sizeof(client_addr)); } } + + // Handle POLLHUP/POLLERR on the UDP send socket + if (fds[1].fd >= 0 && (fds[1].revents & (POLLHUP | POLLERR))) + { + teardown_udp_associate(); + } } - if (socks5_udp_control_socket >= 0) - close(socks5_udp_control_socket); - if (socks5_udp_send_socket >= 0) - close(socks5_udp_send_socket); + teardown_udp_associate(); if (udp_relay_socket >= 0) close(udp_relay_socket); @@ -1841,6 +1907,9 @@ static void* packet_processor(void *arg) rv = recv(fd, buf, sizeof(buf), 0); if (rv >= 0) nfq_handle_packet(nfq_h, buf, rv); + // On error: ENOBUFS = kernel queue full (normal under load), + // EINTR = signal, other = transient. Always continue - + // stopping the thread would break all network traffic. } return NULL; @@ -2294,10 +2363,13 @@ uint32_t ProxyBridge_AddRule(const char* process_name, const char* target_hosts, rule->action = action; rule->enabled = true; + + pthread_rwlock_wrlock(&rules_lock); rule->next = rules_list; rules_list = rule; - update_has_active_rules(); + pthread_rwlock_unlock(&rules_lock); + log_message("added rule id %u for process %s protocol %d action %d", rule->rule_id, process_name, protocol, action); return rule->rule_id; @@ -2308,6 +2380,7 @@ bool ProxyBridge_EnableRule(uint32_t rule_id) if (rule_id == 0) return false; + pthread_rwlock_wrlock(&rules_lock); PROCESS_RULE *rule = rules_list; while (rule != NULL) { @@ -2315,11 +2388,13 @@ bool ProxyBridge_EnableRule(uint32_t rule_id) { rule->enabled = true; update_has_active_rules(); + pthread_rwlock_unlock(&rules_lock); log_message("enabled rule id %u", rule_id); return true; } rule = rule->next; } + pthread_rwlock_unlock(&rules_lock); return false; } @@ -2328,6 +2403,7 @@ bool ProxyBridge_DisableRule(uint32_t rule_id) if (rule_id == 0) return false; + pthread_rwlock_wrlock(&rules_lock); PROCESS_RULE *rule = rules_list; while (rule != NULL) { @@ -2335,11 +2411,13 @@ bool ProxyBridge_DisableRule(uint32_t rule_id) { rule->enabled = false; update_has_active_rules(); + pthread_rwlock_unlock(&rules_lock); log_message("disabled rule id %u", rule_id); return true; } rule = rule->next; } + pthread_rwlock_unlock(&rules_lock); return false; } @@ -2348,6 +2426,7 @@ bool ProxyBridge_DeleteRule(uint32_t rule_id) if (rule_id == 0) return false; + pthread_rwlock_wrlock(&rules_lock); PROCESS_RULE *rule = rules_list; PROCESS_RULE *prev = NULL; @@ -2360,19 +2439,22 @@ bool ProxyBridge_DeleteRule(uint32_t rule_id) else prev->next = rule->next; + update_has_active_rules(); + pthread_rwlock_unlock(&rules_lock); + if (rule->target_hosts != NULL) free(rule->target_hosts); if (rule->target_ports != NULL) free(rule->target_ports); free(rule); - update_has_active_rules(); log_message("deleted rule id %u", rule_id); return true; } prev = rule; rule = rule->next; } + pthread_rwlock_unlock(&rules_lock); return false; } @@ -2381,6 +2463,17 @@ bool ProxyBridge_EditRule(uint32_t rule_id, const char* process_name, const char if (rule_id == 0 || process_name == NULL || target_hosts == NULL || target_ports == NULL) return false; + // Pre-allocate new strings before taking lock to minimize hold time + char *new_hosts = strdup(target_hosts); + char *new_ports = strdup(target_ports); + if (new_hosts == NULL || new_ports == NULL) + { + free(new_hosts); + free(new_ports); + return false; + } + + pthread_rwlock_wrlock(&rules_lock); PROCESS_RULE *rule = rules_list; while (rule != NULL) { @@ -2389,33 +2482,27 @@ bool ProxyBridge_EditRule(uint32_t rule_id, const char* process_name, const char strncpy(rule->process_name, process_name, MAX_PROCESS_NAME - 1); rule->process_name[MAX_PROCESS_NAME - 1] = '\0'; - if (rule->target_hosts != NULL) - free(rule->target_hosts); - rule->target_hosts = strdup(target_hosts); - if (rule->target_hosts == NULL) - { - return false; - } + free(rule->target_hosts); + rule->target_hosts = new_hosts; - if (rule->target_ports != NULL) - free(rule->target_ports); - rule->target_ports = strdup(target_ports); - if (rule->target_ports == NULL) - { - free(rule->target_hosts); - rule->target_hosts = NULL; - return false; - } + free(rule->target_ports); + rule->target_ports = new_ports; rule->protocol = protocol; rule->action = action; update_has_active_rules(); + pthread_rwlock_unlock(&rules_lock); log_message("updated rule id %u", rule_id); return true; } rule = rule->next; } + pthread_rwlock_unlock(&rules_lock); + + // Rule not found - free pre-allocated strings + free(new_hosts); + free(new_ports); return false; } @@ -2491,6 +2578,9 @@ bool ProxyBridge_Start(void) running = true; g_current_process_id = getpid(); + // Ignore SIGPIPE - send() on a closed socket must return EPIPE, not kill the process + signal(SIGPIPE, SIG_IGN); + // Raise system socket buffer limits for high throughput (requires root) // Default rmem_max/wmem_max is usually 208KB, far too small for >100Mbps FILE *fp; @@ -2509,6 +2599,8 @@ bool ProxyBridge_Start(void) { running = false; pthread_cancel(proxy_thread); + pthread_join(proxy_thread, NULL); + proxy_thread = 0; return false; } @@ -2525,8 +2617,7 @@ bool ProxyBridge_Start(void) if (!nfq_h) { log_message("nfq_open failed"); - running = false; - return false; + goto start_fail; } if (nfq_unbind_pf(nfq_h, AF_INET) < 0) @@ -2538,8 +2629,8 @@ bool ProxyBridge_Start(void) { log_message("nfq_bind_pf failed"); nfq_close(nfq_h); - running = false; - return false; + nfq_h = NULL; + goto start_fail; } nfq_qh = nfq_create_queue(nfq_h, 0, &packet_callback, NULL); @@ -2547,21 +2638,18 @@ bool ProxyBridge_Start(void) { log_message("nfq_create_queue failed"); nfq_close(nfq_h); - running = false; - return false; + nfq_h = NULL; + goto start_fail; } if (nfq_set_mode(nfq_qh, NFQNL_COPY_PACKET, 0xffff) < 0) { log_message("nfq_set_mode failed"); nfq_destroy_queue(nfq_qh); + nfq_qh = NULL; nfq_close(nfq_h); - running = false; - pthread_cancel(cleanup_thread); - pthread_cancel(proxy_thread); - if (udp_relay_thread != 0) - pthread_cancel(udp_relay_thread); - return false; + nfq_h = NULL; + goto start_fail; } // Set larger queue length for better performance (16384 like Windows) @@ -2599,6 +2687,13 @@ bool ProxyBridge_Start(void) log_message("proxybridge started"); return true; + +start_fail: + running = false; + if (proxy_thread != 0) { pthread_cancel(proxy_thread); pthread_join(proxy_thread, NULL); proxy_thread = 0; } + if (cleanup_thread != 0) { pthread_cancel(cleanup_thread); pthread_join(cleanup_thread, NULL); cleanup_thread = 0; } + if (udp_relay_thread != 0) { pthread_cancel(udp_relay_thread); pthread_join(udp_relay_thread, NULL); udp_relay_thread = 0; } + return false; } bool ProxyBridge_Stop(void) @@ -2674,6 +2769,20 @@ bool ProxyBridge_Stop(void) } pthread_rwlock_unlock(&conn_lock); + // Free all rules + pthread_rwlock_wrlock(&rules_lock); + while (rules_list != NULL) + { + PROCESS_RULE *to_free = rules_list; + rules_list = rules_list->next; + free(to_free->target_hosts); + free(to_free->target_ports); + free(to_free); + } + g_has_active_rules = false; + g_next_rule_id = 1; + pthread_rwlock_unlock(&rules_lock); + clear_logged_connections(); clear_pid_cache(); diff --git a/Windows/src/ProxyBridge.c b/Windows/src/ProxyBridge.c index 9f2a4c6..b5d6750 100644 --- a/Windows/src/ProxyBridge.c +++ b/Windows/src/ProxyBridge.c @@ -1469,6 +1469,10 @@ static BOOL establish_udp_associate(void) return FALSE; } + // RFC 1928: if server returns 0.0.0.0 as relay address, use the proxy server's IP + if (socks5_udp_relay_addr.sin_addr.s_addr == INADDR_ANY) + socks5_udp_relay_addr.sin_addr.s_addr = socks5_ip; + socks5_udp_socket = tcp_sock; // ccreate UDP socket for sending to SOCKS5 proxy From 9b729f9bfd8efe736ae79bc9aa0b5b935445aa5c Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 18:38:28 +0530 Subject: [PATCH 14/58] added inital GUI --- Linux/build.sh | 20 +- Linux/gui/main.c | 720 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 610 insertions(+), 130 deletions(-) diff --git a/Linux/build.sh b/Linux/build.sh index 868b8b4..ed07efc 100755 --- a/Linux/build.sh +++ b/Linux/build.sh @@ -44,9 +44,27 @@ fi echo "" # Move binaries to output +echo "=== Building GUI ===" +cd "$SCRIPT_DIR" +rm -f ProxyBridgeGUI +if pkg-config --exists gtk+-3.0; then + gcc -Wall -Wno-unused-parameter -O3 -Isrc -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security -Werror=format-security -fno-strict-overflow -fno-delete-null-pointer-checks -fwrapv -c gui/main.c -o gui_main.o $(pkg-config --cflags gtk+-3.0) + gcc -o ProxyBridgeGUI gui_main.o -Lsrc -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s -Wl,-rpath,'$ORIGIN/.' -lproxybridge -lpthread $(pkg-config --libs gtk+-3.0) -export-dynamic + rm -f gui_main.o + echo "✓ GUI build successful" +else + echo "⚠ GTK3 not found. Skipping GUI build." + echo " Install with: sudo apt install libgtk-3-dev (Debian/Ubuntu/Mint)" + echo " sudo dnf install gtk3-devel (Fedora)" +fi + +echo "" echo "Moving binaries to output directory..." mv "$SCRIPT_DIR/src/libproxybridge.so" "$OUTPUT_DIR/" -mv ProxyBridge "$OUTPUT_DIR/" +mv "$SCRIPT_DIR/cli/ProxyBridge" "$OUTPUT_DIR/" +if [ -f ProxyBridgeGUI ]; then + mv ProxyBridgeGUI "$OUTPUT_DIR/" +fi echo "✓ Binaries moved to output" echo "" diff --git a/Linux/gui/main.c b/Linux/gui/main.c index 399f27b..afac9fa 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -1,152 +1,614 @@ -#include -#include -#include #include -#include -#include "proxybridge.h" -#include "main_window.h" - -// Library handle -static void *lib_handle = NULL; - -// Function pointers -uint32_t (*ProxyBridge_AddRule)(const char *, const char *, const char *, RuleProtocol, RuleAction) = NULL; -bool (*ProxyBridge_EnableRule)(uint32_t) = NULL; -bool (*ProxyBridge_DisableRule)(uint32_t) = NULL; -bool (*ProxyBridge_DeleteRule)(uint32_t) = NULL; -bool (*ProxyBridge_EditRule)(uint32_t, const char *, const char *, const char *, RuleProtocol, RuleAction) = NULL; -bool (*ProxyBridge_SetProxyConfig)(ProxyType, const char *, uint16_t, const char *, const char *) = NULL; -void (*ProxyBridge_SetLogCallback)(LogCallback) = NULL; -void (*ProxyBridge_SetConnectionCallback)(ConnectionCallback) = NULL; -void (*ProxyBridge_SetTrafficLoggingEnabled)(bool) = NULL; -void (*ProxyBridge_SetDnsViaProxy)(bool) = NULL; -bool (*ProxyBridge_Start)(void) = NULL; -bool (*ProxyBridge_Stop)(void) = NULL; - -// Callback handlers -static void log_callback(const char *message) { - add_activity_log(message); -} - -static void connection_callback(const char *processName, uint32_t pid, - const char *destIp, uint16_t destPort, - const char *proxyInfo) { - char msg[512]; - snprintf(msg, sizeof(msg), "%s (PID %u) → %s:%u via %s", - processName, pid, destIp, destPort, proxyInfo); - add_connection_log(msg); -} - -bool load_proxybridge_library(void) -{ - lib_handle = dlopen("libproxybridge.so", RTLD_LAZY); - if (!lib_handle) { - lib_handle = dlopen("/usr/local/lib/libproxybridge.so", RTLD_LAZY); - } - - if (!lib_handle) { - fprintf(stderr, "Failed to load libproxybridge.so: %s\n", dlerror()); - return false; +#include +#include +#include +#include +#include +#include +#include "ProxyBridge.h" + +// --- Global UI Widgets --- +static GtkWidget *window; +static GtkTextView *conn_view; +static GtkTextBuffer *conn_buffer; +static GtkTextView *log_view; +static GtkTextBuffer *log_buffer; +static GtkWidget *status_bar; +static guint status_context_id; + +// --- Data Structures for thread-safe UI updates --- +typedef struct { + char *process_name; + uint32_t pid; + char *dest_ip; + uint16_t dest_port; + char *proxy_info; + char *timestamp; +} ConnectionData; + +typedef struct { + char *message; +} LogData; + +typedef struct { + GtkWidget *dialog; + GtkWidget *ip_entry; + GtkWidget *port_entry; + GtkWidget *type_combo; + GtkWidget *user_entry; + GtkWidget *pass_entry; + GtkWidget *test_host; + GtkWidget *test_port; + GtkTextBuffer *output_buffer; + GtkWidget *test_btn; +} ConfigInfo; + +// --- Config Globals (Defaults) --- +static char g_proxy_ip[256] = ""; +static uint16_t g_proxy_port = 0; +static ProxyType g_proxy_type = PROXY_TYPE_SOCKS5; +static char g_proxy_user[256] = ""; +static char g_proxy_pass[256] = ""; + +// --- Helper Functions --- + +static char* get_current_time_str() { + time_t rawtime; + struct tm *timeinfo; + char *buffer = malloc(32); + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(buffer, 32, "[%H:%M:%S]", timeinfo); + return buffer; +} + +static void free_connection_data(ConnectionData *data) { + if (data) { + free(data->process_name); + free(data->dest_ip); + free(data->proxy_info); + free(data->timestamp); + free(data); } +} + +// --- GTK Callbacks (Main Thread) --- + +// Filter Function for TextView +static void filter_text_view(GtkTextBuffer *buffer, const char *text) { + if (!buffer) return; - #define LOAD_SYMBOL(name) \ - name = dlsym(lib_handle, #name); \ - if (!name) { \ - fprintf(stderr, "Failed to load symbol %s: %s\n", #name, dlerror()); \ - dlclose(lib_handle); \ - return false; \ + GtkTextIter start, end; + gtk_text_buffer_get_bounds(buffer, &start, &end); + gtk_text_buffer_remove_tag_by_name(buffer, "hidden", &start, &end); + + if (!text || strlen(text) == 0) return; + + GtkTextIter line_start = start; + while (!gtk_text_iter_is_end(&line_start)) { + GtkTextIter line_end = line_start; + if (!gtk_text_iter_ends_line(&line_end)) + gtk_text_iter_forward_to_line_end(&line_end); + + char *line_text = gtk_text_buffer_get_text(buffer, &line_start, &line_end, FALSE); + + // Case-insensitive search + char *lower_line = g_utf8_strdown(line_text, -1); + char *lower_search = g_utf8_strdown(text, -1); + + if (!strstr(lower_line, lower_search)) { + GtkTextIter next_line = line_end; + gtk_text_iter_forward_char(&next_line); // include newline + gtk_text_buffer_apply_tag_by_name(buffer, "hidden", &line_start, &next_line); } + + g_free(lower_line); + g_free(lower_search); + g_free(line_text); + + gtk_text_iter_forward_line(&line_start); + } +} + +static void on_search_conn_changed(GtkSearchEntry *entry, gpointer user_data) { + const char *text = gtk_entry_get_text(GTK_ENTRY(entry)); + filter_text_view(conn_buffer, text); +} + +static void on_search_log_changed(GtkSearchEntry *entry, gpointer user_data) { + const char *text = gtk_entry_get_text(GTK_ENTRY(entry)); + filter_text_view(log_buffer, text); +} + +static void on_clear_conn_clicked(GtkButton *button, gpointer user_data) { + if (conn_buffer) gtk_text_buffer_set_text(conn_buffer, "", 0); +} + +static void on_clear_log_clicked(GtkButton *button, gpointer user_data) { + if (log_buffer) gtk_text_buffer_set_text(log_buffer, "", 0); +} + +// Thread-safe idle callback for Log +static gboolean update_log_gui(gpointer user_data) { + LogData *data = (LogData *)user_data; + if (!data) return FALSE; + + GtkTextIter end; + gtk_text_buffer_get_end_iter(log_buffer, &end); - LOAD_SYMBOL(ProxyBridge_AddRule); - LOAD_SYMBOL(ProxyBridge_EnableRule); - LOAD_SYMBOL(ProxyBridge_DisableRule); - LOAD_SYMBOL(ProxyBridge_DeleteRule); - LOAD_SYMBOL(ProxyBridge_EditRule); - LOAD_SYMBOL(ProxyBridge_SetProxyConfig); - LOAD_SYMBOL(ProxyBridge_SetLogCallback); - LOAD_SYMBOL(ProxyBridge_SetConnectionCallback); - LOAD_SYMBOL(ProxyBridge_SetTrafficLoggingEnabled); - LOAD_SYMBOL(ProxyBridge_SetDnsViaProxy); - LOAD_SYMBOL(ProxyBridge_Start); - LOAD_SYMBOL(ProxyBridge_Stop); - - #undef LOAD_SYMBOL - - // Set up callbacks - ProxyBridge_SetLogCallback(log_callback); - ProxyBridge_SetConnectionCallback(connection_callback); - - return true; -} - -void unload_proxybridge_library(void) -{ - if (lib_handle) { - // Stop proxy before unloading (critical cleanup) - if (ProxyBridge_Stop) { - ProxyBridge_Stop(); - } - dlclose(lib_handle); - lib_handle = NULL; + char full_msg[1200]; + snprintf(full_msg, sizeof(full_msg), "%s %s\n", get_current_time_str(), data->message); + gtk_text_buffer_insert(log_buffer, &end, full_msg, -1); + + // Limit to 100 lines to prevent memory growth + while (gtk_text_buffer_get_line_count(log_buffer) > 100) { + GtkTextIter start, next; + gtk_text_buffer_get_start_iter(log_buffer, &start); + next = start; + gtk_text_iter_forward_line(&next); + gtk_text_buffer_delete(log_buffer, &start, &next); } + + free(data->message); + free(data); + return FALSE; // Remove source } -static gboolean check_root_privileges(void) -{ - if (geteuid() != 0) { - GtkWidget *dialog = gtk_message_dialog_new( - NULL, - GTK_DIALOG_MODAL, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_OK, - "ProxyBridge requires root privileges" - ); - gtk_message_dialog_format_secondary_text( - GTK_MESSAGE_DIALOG(dialog), - "Please run with: sudo ./ProxyBridge-GUI" - ); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - return FALSE; +// Thread-safe idle callback for Connection +static gboolean update_connection_gui_append(gpointer user_data) { + ConnectionData *data = (ConnectionData *)user_data; + if (!data) return FALSE; + + if (conn_buffer) { + GtkTextIter end; + gtk_text_buffer_get_end_iter(conn_buffer, &end); + + char line_buffer[1024]; + // Format: [Time] Process (PID) -> Target via ProxyInfo + snprintf(line_buffer, sizeof(line_buffer), "%s %s (PID:%u) -> %s:%u via %s\n", + data->timestamp, data->process_name, data->pid, data->dest_ip, data->dest_port, data->proxy_info); + + gtk_text_buffer_insert(conn_buffer, &end, line_buffer, -1); + + // Limit to 100 lines to prevent memory growth + while (gtk_text_buffer_get_line_count(conn_buffer) > 100) { + GtkTextIter start, next; + gtk_text_buffer_get_start_iter(conn_buffer, &start); + next = start; + gtk_text_iter_forward_line(&next); + gtk_text_buffer_delete(conn_buffer, &start, &next); + } } - return TRUE; + + free_connection_data(data); + return FALSE; } -int main(int argc, char *argv[]) -{ - gtk_init(&argc, &argv); + +// --- Library Callbacks --- + +static void lib_log_callback(const char *message) { + LogData *data = malloc(sizeof(LogData)); + data->message = strdup(message); + g_idle_add(update_log_gui, data); +} + +static void lib_connection_callback(const char *process_name, uint32_t pid, const char *dest_ip, uint16_t dest_port, const char *proxy_info) { + ConnectionData *data = malloc(sizeof(ConnectionData)); + data->process_name = strdup(process_name); + data->pid = pid; + data->dest_ip = strdup(dest_ip); + data->dest_port = dest_port; + data->proxy_info = strdup(proxy_info); + data->timestamp = get_current_time_str(); + g_idle_add(update_connection_gui_append, data); +} + +// --- Settings Dialog --- + +struct TestRunnerData { + char *host; + uint16_t port; + ConfigInfo *ui_info; +}; + +// Better thread communication +typedef struct { + char *result_text; + GtkTextBuffer *buffer; + GtkWidget *btn; +} TestResultData; + +static gboolean on_test_done(gpointer user_data) { + TestResultData *data = (TestResultData *)user_data; + gtk_text_buffer_set_text(data->buffer, data->result_text, -1); + gtk_widget_set_sensitive(data->btn, TRUE); - // Check root privileges - if (!check_root_privileges()) { - return 1; + free(data->result_text); + free(data); + return FALSE; +} + +static gpointer run_test_thread(gpointer user_data) { + struct TestRunnerData *req = (struct TestRunnerData *)user_data; + + char *buffer = malloc(4096); + memset(buffer, 0, 4096); + + // Check callback if needed, but TestConnection usually returns result in buffer + ProxyBridge_TestConnection(req->host, req->port, buffer, 4096); + + TestResultData *res = malloc(sizeof(TestResultData)); + res->result_text = buffer; + res->buffer = req->ui_info->output_buffer; + res->btn = req->ui_info->test_btn; + + g_idle_add(on_test_done, res); + + free(req->host); + free(req); + return NULL; +} + +static void on_start_test_clicked(GtkWidget *widget, gpointer data) { + ConfigInfo *info = (ConfigInfo *)data; + + // 1. Validate Proxy Config + const char *ip_text = gtk_entry_get_text(GTK_ENTRY(info->ip_entry)); + const char *port_text = gtk_entry_get_text(GTK_ENTRY(info->port_entry)); + + if (!ip_text || strlen(ip_text) == 0 || strspn(port_text, "0123456789") != strlen(port_text) || strlen(port_text) == 0) { + gtk_text_buffer_set_text(info->output_buffer, "Error: Invalid Proxy IP or Port.", -1); + return; } - // Load ProxyBridge library - if (!load_proxybridge_library()) { - GtkWidget *dialog = gtk_message_dialog_new( - NULL, - GTK_DIALOG_MODAL, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_OK, - "Failed to load ProxyBridge library" - ); - gtk_message_dialog_format_secondary_text( - GTK_MESSAGE_DIALOG(dialog), - "Make sure libproxybridge.so is in /usr/local/lib/" - ); + // 2. Set Proxy Config + ProxyType type = (gtk_combo_box_get_active(GTK_COMBO_BOX(info->type_combo)) == 0) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; + int port = atoi(port_text); + const char *user = gtk_entry_get_text(GTK_ENTRY(info->user_entry)); + const char *pass = gtk_entry_get_text(GTK_ENTRY(info->pass_entry)); + + ProxyBridge_SetProxyConfig(type, ip_text, port, user, pass); + + // 3. Get Test Target + const char *t_host = gtk_entry_get_text(GTK_ENTRY(info->test_host)); + const char *t_port_s = gtk_entry_get_text(GTK_ENTRY(info->test_port)); + + if (!t_host || strlen(t_host) == 0) t_host = "google.com"; + int t_port = atoi(t_port_s); + if (t_port <= 0) t_port = 80; + + // 4. Update UI + gtk_text_buffer_set_text(info->output_buffer, "Testing connection... Please wait...", -1); + gtk_widget_set_sensitive(info->test_btn, FALSE); + + // 5. Run Thread + struct TestRunnerData *req = malloc(sizeof(struct TestRunnerData)); + req->host = strdup(t_host); + req->port = t_port; + req->ui_info = info; + + GThread *thread = g_thread_new("test_conn", run_test_thread, req); + g_thread_unref(thread); +} + +static void on_proxy_configure(GtkWidget *widget, gpointer data) { + ConfigInfo info; // We will pass address to signals, careful about scope if dialog is non-modal? No, dialog_run is blocking. + // Wait, on_start_test_clicked will be called while dialog_run is blocking main loop? Yes. + // But info needs to persist. Stack allocation is fine because we don't leave this function until dialog closes. + + GtkWidget *content_area; + GtkWidget *grid; + // Removed local declarations that are now in struct ConfigInfo + + info.dialog = gtk_dialog_new_with_buttons("Proxy Settings", + GTK_WINDOW(window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + "Cancel", GTK_RESPONSE_CANCEL, + "Save", GTK_RESPONSE_ACCEPT, + NULL); + // Increase width for log + gtk_window_set_default_size(GTK_WINDOW(info.dialog), 600, 500); + + content_area = gtk_dialog_get_content_area(GTK_DIALOG(info.dialog)); + grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(grid), 5); + gtk_grid_set_column_spacing(GTK_GRID(grid), 10); + gtk_container_set_border_width(GTK_CONTAINER(grid), 10); + gtk_box_pack_start(GTK_BOX(content_area), grid, TRUE, TRUE, 0); + + // Type + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Type:"), 0, 0, 1, 1); + info.type_combo = gtk_combo_box_text_new(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(info.type_combo), "HTTP"); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(info.type_combo), "SOCKS5"); + gtk_combo_box_set_active(GTK_COMBO_BOX(info.type_combo), g_proxy_type == PROXY_TYPE_HTTP ? 0 : 1); + gtk_grid_attach(GTK_GRID(grid), info.type_combo, 1, 0, 3, 1); // Span 3 + + // IP + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Host:"), 0, 1, 1, 1); + info.ip_entry = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(info.ip_entry), g_proxy_ip); + gtk_widget_set_hexpand(info.ip_entry, TRUE); + gtk_grid_attach(GTK_GRID(grid), info.ip_entry, 1, 1, 3, 1); + + // Port + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Port:"), 0, 2, 1, 1); + info.port_entry = gtk_entry_new(); + if (g_proxy_port != 0) { + char port_str[8]; + sprintf(port_str, "%d", g_proxy_port); + gtk_entry_set_text(GTK_ENTRY(info.port_entry), port_str); + } + gtk_grid_attach(GTK_GRID(grid), info.port_entry, 1, 2, 3, 1); + + // User + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Username:"), 0, 3, 1, 1); + info.user_entry = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(info.user_entry), g_proxy_user); + gtk_grid_attach(GTK_GRID(grid), info.user_entry, 1, 3, 3, 1); + + // Pass + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Password:"), 0, 4, 1, 1); + info.pass_entry = gtk_entry_new(); + gtk_entry_set_visibility(GTK_ENTRY(info.pass_entry), FALSE); + gtk_entry_set_text(GTK_ENTRY(info.pass_entry), g_proxy_pass); + gtk_grid_attach(GTK_GRID(grid), info.pass_entry, 1, 4, 3, 1); + + // --- Test Section --- + gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 5, 4, 1); + + GtkWidget *test_label = gtk_label_new("Test Connection"); + gtk_label_set_use_markup(GTK_LABEL(test_label), TRUE); + gtk_widget_set_halign(test_label, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(grid), test_label, 0, 6, 4, 1); + + // Target Host + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target:"), 0, 7, 1, 1); + info.test_host = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(info.test_host), "google.com"); + gtk_grid_attach(GTK_GRID(grid), info.test_host, 1, 7, 1, 1); + + // Target Port + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Port:"), 2, 7, 1, 1); + info.test_port = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(info.test_port), "80"); + gtk_widget_set_size_request(info.test_port, 80, -1); + gtk_grid_attach(GTK_GRID(grid), info.test_port, 3, 7, 1, 1); + + // Start Test Button + info.test_btn = gtk_button_new_with_label("Start Test"); + g_signal_connect(info.test_btn, "clicked", G_CALLBACK(on_start_test_clicked), &info); + gtk_grid_attach(GTK_GRID(grid), info.test_btn, 0, 8, 4, 1); + + // Output Log + GtkWidget *out_scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_size_request(out_scroll, -1, 150); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(out_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + GtkWidget *out_view = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(out_view), FALSE); + gtk_text_view_set_monospace(GTK_TEXT_VIEW(out_view), TRUE); + info.output_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(out_view)); + + gtk_container_add(GTK_CONTAINER(out_scroll), out_view); + gtk_grid_attach(GTK_GRID(grid), out_scroll, 0, 9, 4, 1); + + + gtk_widget_show_all(info.dialog); + + while (TRUE) { + if (gtk_dialog_run(GTK_DIALOG(info.dialog)) != GTK_RESPONSE_ACCEPT) break; + + // Validation + const char *ip_text = gtk_entry_get_text(GTK_ENTRY(info.ip_entry)); + const char *port_text = gtk_entry_get_text(GTK_ENTRY(info.port_entry)); + + if (!ip_text || strlen(ip_text) == 0) { + GtkWidget *err = gtk_message_dialog_new(GTK_WINDOW(info.dialog), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Host (IP/Domain) cannot be empty."); + gtk_dialog_run(GTK_DIALOG(err)); + gtk_widget_destroy(err); + continue; + } + + if (strspn(port_text, "0123456789") != strlen(port_text) || strlen(port_text) == 0) { + GtkWidget *err = gtk_message_dialog_new(GTK_WINDOW(info.dialog), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Port must be a valid number."); + gtk_dialog_run(GTK_DIALOG(err)); + gtk_widget_destroy(err); + continue; + } + + int p = atoi(port_text); + if (p < 1 || p > 65535) { + GtkWidget *err = gtk_message_dialog_new(GTK_WINDOW(info.dialog), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Port must be between 1 and 65535."); + gtk_dialog_run(GTK_DIALOG(err)); + gtk_widget_destroy(err); + continue; + } + + // Save + g_proxy_type = (gtk_combo_box_get_active(GTK_COMBO_BOX(info.type_combo)) == 0) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; + strncpy(g_proxy_ip, ip_text, sizeof(g_proxy_ip)-1); + g_proxy_port = p; + strncpy(g_proxy_user, gtk_entry_get_text(GTK_ENTRY(info.user_entry)), sizeof(g_proxy_user)-1); + strncpy(g_proxy_pass, gtk_entry_get_text(GTK_ENTRY(info.pass_entry)), sizeof(g_proxy_pass)-1); + + ProxyBridge_SetProxyConfig(g_proxy_type, g_proxy_ip, g_proxy_port, g_proxy_user, g_proxy_pass); + + char status_msg[512]; + snprintf(status_msg, sizeof(status_msg), "Configuration updated: %s:%d", g_proxy_ip, g_proxy_port); + gtk_statusbar_push(GTK_STATUSBAR(status_bar), status_context_id, status_msg); + break; + } + + gtk_widget_destroy(info.dialog); +} + +static void on_about(GtkWidget *widget, gpointer data) { + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_OK, + "ProxyBridge Linux GUI\nVersion 4.0-Beta\n\nHigh-performance zero-copy proxy client."); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +static void signal_handler(int sig) { + fprintf(stderr, "\nSignal %d received. Stopping ProxyBridge...\n", sig); + ProxyBridge_Stop(); + exit(sig); +} + +static void on_window_destroy(GtkWidget *widget, gpointer data) { + ProxyBridge_Stop(); + gtk_main_quit(); +} + +// --- Main Init --- + +int main(int argc, char *argv[]) { + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGSEGV, signal_handler); + + // Only verify root if not argument --help or similar passed? + // But GTK also takes args. + // Simply check uid. + if (getuid() != 0) { + // Can't show GUI dialog easily without GTK init which might fail if no display. + // Try init, then dialog. + gtk_init(&argc, &argv); + GtkWidget *dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "ProxyBridge must be run as root (sudo)."); gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); return 1; } + + gtk_init(&argc, &argv); + + // Apply dark theme preference if available + GtkSettings *settings = gtk_settings_get_default(); + if (settings) { + g_object_set(settings, "gtk-application-prefer-dark-theme", TRUE, NULL); + } + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), "ProxyBridge"); + gtk_window_set_default_size(GTK_WINDOW(window), 900, 600); + g_signal_connect(window, "destroy", G_CALLBACK(on_window_destroy), NULL); + + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + + // Menu Bar + GtkWidget *menubar = gtk_menu_bar_new(); - // Create main window - create_main_window(); + GtkWidget *proxy_menu_item = gtk_menu_item_new_with_label("Proxy"); + GtkWidget *proxy_menu = gtk_menu_new(); + GtkWidget *config_item = gtk_menu_item_new_with_label("Proxy Settings"); + GtkWidget *exit_item = gtk_menu_item_new_with_label("Exit"); + g_signal_connect(config_item, "activate", G_CALLBACK(on_proxy_configure), NULL); + g_signal_connect(exit_item, "activate", G_CALLBACK(on_window_destroy), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), config_item); + gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), gtk_separator_menu_item_new()); + gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), exit_item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(proxy_menu_item), proxy_menu); - // Run GTK main loop - gtk_main(); + GtkWidget *about_menu_item = gtk_menu_item_new_with_label("About"); + g_signal_connect(about_menu_item, "activate", G_CALLBACK(on_about), NULL); + + gtk_menu_shell_append(GTK_MENU_SHELL(menubar), proxy_menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(menubar), about_menu_item); + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); + + // Tabs + GtkWidget *notebook = gtk_notebook_new(); + gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); + + // --- Tab 1: Connections --- + GtkWidget *conn_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + gtk_container_set_border_width(GTK_CONTAINER(conn_vbox), 5); + + // Toolbar (Search + Clear) + GtkWidget *conn_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + + GtkWidget *conn_search = gtk_search_entry_new(); + gtk_entry_set_placeholder_text(GTK_ENTRY(conn_search), "Search connections..."); + g_signal_connect(conn_search, "search-changed", G_CALLBACK(on_search_conn_changed), NULL); + + GtkWidget *conn_clear_btn = gtk_button_new_with_label("Clear Logs"); + g_signal_connect(conn_clear_btn, "clicked", G_CALLBACK(on_clear_conn_clicked), NULL); + + gtk_box_pack_start(GTK_BOX(conn_toolbar), conn_search, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(conn_toolbar), conn_clear_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(conn_vbox), conn_toolbar, FALSE, FALSE, 0); + + // List View (Now Text View) + conn_view = GTK_TEXT_VIEW(gtk_text_view_new()); + gtk_text_view_set_editable(conn_view, FALSE); + gtk_text_view_set_cursor_visible(conn_view, FALSE); + conn_buffer = gtk_text_view_get_buffer(conn_view); + + // Create tag for filtering + gtk_text_buffer_create_tag(conn_buffer, "hidden", "invisible", TRUE, NULL); + + GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(scrolled_window), GTK_WIDGET(conn_view)); + gtk_box_pack_start(GTK_BOX(conn_vbox), scrolled_window, TRUE, TRUE, 0); + + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), conn_vbox, gtk_label_new("Connections")); + + // --- Tab 2: Activity Logs --- + GtkWidget *log_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + gtk_container_set_border_width(GTK_CONTAINER(log_vbox), 5); + + // Toolbar (Search + Clear) + GtkWidget *log_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + + GtkWidget *log_search = gtk_search_entry_new(); + gtk_entry_set_placeholder_text(GTK_ENTRY(log_search), "Search logs..."); + g_signal_connect(log_search, "search-changed", G_CALLBACK(on_search_log_changed), NULL); + + GtkWidget *log_clear_btn = gtk_button_new_with_label("Clear Logs"); + g_signal_connect(log_clear_btn, "clicked", G_CALLBACK(on_clear_log_clicked), NULL); + + gtk_box_pack_start(GTK_BOX(log_toolbar), log_search, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(log_toolbar), log_clear_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(log_vbox), log_toolbar, FALSE, FALSE, 0); + + log_view = GTK_TEXT_VIEW(gtk_text_view_new()); + gtk_text_view_set_editable(log_view, FALSE); + gtk_text_view_set_cursor_visible(log_view, FALSE); + log_buffer = gtk_text_view_get_buffer(log_view); + + // Create tag for filtering + gtk_text_buffer_create_tag(log_buffer, "hidden", "invisible", TRUE, NULL); - // Cleanup - unload_proxybridge_library(); + GtkWidget *log_scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(log_scroll), GTK_WIDGET(log_view)); + gtk_box_pack_start(GTK_BOX(log_vbox), log_scroll, TRUE, TRUE, 0); + + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), log_vbox, gtk_label_new("Activity Logs")); + + // Status Bar + status_bar = gtk_statusbar_new(); + status_context_id = gtk_statusbar_get_context_id(GTK_STATUSBAR(status_bar), "Status"); + gtk_box_pack_start(GTK_BOX(vbox), status_bar, FALSE, FALSE, 0); + + // Initial Config + ProxyBridge_SetLogCallback(lib_log_callback); + ProxyBridge_SetConnectionCallback(lib_connection_callback); + ProxyBridge_SetTrafficLoggingEnabled(true); + // Start Proxy Engine + if (ProxyBridge_Start()) { + gtk_statusbar_push(GTK_STATUSBAR(status_bar), status_context_id, "ProxyBridge Service Started. Please configure proxy settings."); + } else { + gtk_statusbar_push(GTK_STATUSBAR(status_bar), status_context_id, "Failed to start ProxyBridge engine."); + } + + gtk_widget_show_all(window); + gtk_main(); + return 0; } From daa66a03e5bf8ace9effa5b42b25b336597be01f Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 19:17:33 +0530 Subject: [PATCH 15/58] added inital proxy rule in GUI --- Linux/gui/main.c | 394 ++++++++++++++++++++++++++++++++++++++++ Linux/src/ProxyBridge.c | 29 ++- 2 files changed, 419 insertions(+), 4 deletions(-) diff --git a/Linux/gui/main.c b/Linux/gui/main.c index afac9fa..fc3f042 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -50,7 +50,31 @@ static ProxyType g_proxy_type = PROXY_TYPE_SOCKS5; static char g_proxy_user[256] = ""; static char g_proxy_pass[256] = ""; +typedef struct { + uint32_t id; + char *process_name; + char *target_hosts; + char *target_ports; + RuleProtocol protocol; + RuleAction action; + bool enabled; +} RuleData; + +static GList *g_rules_list = NULL; +static GtkWidget *rules_list_box = NULL; + // --- Helper Functions --- +// Forward declaration +static void refresh_rules_ui(); + +static void free_rule_data(RuleData *rule) { + if (rule) { + if (rule->process_name) free(rule->process_name); + if (rule->target_hosts) free(rule->target_hosts); + if (rule->target_ports) free(rule->target_ports); + free(rule); + } +} static char* get_current_time_str() { time_t rawtime; @@ -444,6 +468,371 @@ static void on_proxy_configure(GtkWidget *widget, gpointer data) { gtk_widget_destroy(info.dialog); } +// --- Rules Management --- + +static void on_rule_delete(GtkWidget *widget, gpointer data) { + RuleData *rule = (RuleData *)data; + + // Call C API + ProxyBridge_DeleteRule(rule->id); + + // Remove from list + g_rules_list = g_list_remove(g_rules_list, rule); + free_rule_data(rule); + + // UI Refresh + refresh_rules_ui(); +} + +static void on_rule_toggle(GtkToggleButton *btn, gpointer data) { + RuleData *rule = (RuleData *)data; + rule->enabled = gtk_toggle_button_get_active(btn); + + if (rule->enabled) { + ProxyBridge_EnableRule(rule->id); + } else { + ProxyBridge_DisableRule(rule->id); + } +} + +static void on_save_rule(GtkWidget *widget, gpointer data) { + GtkWidget **widgets = (GtkWidget **)data; + GtkWidget *dialog = widgets[0]; + RuleData *edit_rule = (RuleData *)widgets[6]; // If not null, we are editing + + const char *proc = gtk_entry_get_text(GTK_ENTRY(widgets[1])); + const char *hosts = gtk_entry_get_text(GTK_ENTRY(widgets[2])); + const char *ports = gtk_entry_get_text(GTK_ENTRY(widgets[3])); + RuleProtocol proto = gtk_combo_box_get_active(GTK_COMBO_BOX(widgets[4])); + RuleAction action = gtk_combo_box_get_active(GTK_COMBO_BOX(widgets[5])); + + if (strlen(proc) == 0) { + // Error + return; + } + + if (edit_rule) { + // Edit Existing + ProxyBridge_EditRule(edit_rule->id, proc, hosts, ports, proto, action); + + // Update Local + free(edit_rule->process_name); edit_rule->process_name = strdup(proc); + free(edit_rule->target_hosts); edit_rule->target_hosts = strdup(hosts); + free(edit_rule->target_ports); edit_rule->target_ports = strdup(ports); + edit_rule->protocol = proto; + edit_rule->action = action; + + } else { + // Add New + uint32_t new_id = ProxyBridge_AddRule(proc, hosts, ports, proto, action); + + RuleData *new_rule = malloc(sizeof(RuleData)); + new_rule->id = new_id; + new_rule->process_name = strdup(proc); + new_rule->target_hosts = strdup(hosts); + new_rule->target_ports = strdup(ports); + new_rule->protocol = proto; + new_rule->action = action; + new_rule->enabled = true; // Default enabled + + g_rules_list = g_list_append(g_rules_list, new_rule); + } + + refresh_rules_ui(); + gtk_widget_destroy(dialog); + free(widgets); +} + +static void on_browse_clicked(GtkWidget *widget, gpointer data) { + GtkWidget *entry = (GtkWidget *)data; + GtkWidget *dialog = gtk_file_chooser_dialog_new("Select Application", + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Select", GTK_RESPONSE_ACCEPT, + NULL); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + char *base = g_path_get_basename(filename); + gtk_entry_set_text(GTK_ENTRY(entry), base); + g_free(base); + g_free(filename); + } + gtk_widget_destroy(dialog); +} + +static void open_rule_dialog(RuleData *rule) { + GtkWidget *dialog = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(dialog), rule ? "Edit Rule" : "Add Rule"); + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window)); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 400); + + GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + GtkWidget *grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(grid), 8); + gtk_grid_set_column_spacing(GTK_GRID(grid), 10); + gtk_container_set_border_width(GTK_CONTAINER(grid), 15); + + // Process + GtkWidget *proc_entry = gtk_entry_new(); + GtkWidget *browse_btn = gtk_button_new_with_label("Browse..."); + g_signal_connect(browse_btn, "clicked", G_CALLBACK(on_browse_clicked), proc_entry); + + GtkWidget *proc_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_pack_start(GTK_BOX(proc_box), proc_entry, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(proc_box), browse_btn, FALSE, FALSE, 0); + + GtkWidget *host_entry = gtk_entry_new(); + GtkWidget *port_entry = gtk_entry_new(); + + // Protocol + GtkWidget *proto_combo = gtk_combo_box_text_new(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "TCP"); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "UDP"); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "BOTH"); + + // Action + GtkWidget *action_combo = gtk_combo_box_text_new(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "PROXY"); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "DIRECT"); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "BLOCK"); + + if (rule) { + gtk_entry_set_text(GTK_ENTRY(proc_entry), rule->process_name); + gtk_entry_set_text(GTK_ENTRY(host_entry), rule->target_hosts); + gtk_entry_set_text(GTK_ENTRY(port_entry), rule->target_ports); + gtk_combo_box_set_active(GTK_COMBO_BOX(proto_combo), rule->protocol); + gtk_combo_box_set_active(GTK_COMBO_BOX(action_combo), rule->action); + } else { + gtk_entry_set_text(GTK_ENTRY(port_entry), "*"); // Default + gtk_entry_set_text(GTK_ENTRY(host_entry), "*"); // Default + gtk_combo_box_set_active(GTK_COMBO_BOX(proto_combo), 2); // BOTH + gtk_combo_box_set_active(GTK_COMBO_BOX(action_combo), 0); // PROXY + } + + int row = 0; + + // Process Row + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Process Name:"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), proc_box, 1, row, 1, 1); + row++; + + // Process Hint + GtkWidget *proc_hint = gtk_label_new("Example: firefox; chrome; /usr/bin/wget"); + gtk_style_context_add_class(gtk_widget_get_style_context(proc_hint), "dim-label"); + gtk_widget_set_halign(proc_hint, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(grid), proc_hint, 1, row, 1, 1); + row++; + + // Host Row + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target Host:"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), host_entry, 1, row, 1, 1); + row++; + + // Host Hint + GtkWidget *host_hint = gtk_label_new("Example: 192.168.1.*; 10.0.0.1-50; *"); + gtk_style_context_add_class(gtk_widget_get_style_context(host_hint), "dim-label"); + gtk_widget_set_halign(host_hint, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(grid), host_hint, 1, row, 1, 1); + row++; + + // Port Row + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target Port:"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), port_entry, 1, row, 1, 1); + row++; + + // Port Hint + GtkWidget *port_hint = gtk_label_new("Example: 80; 443; 8000-8080; *"); + gtk_style_context_add_class(gtk_widget_get_style_context(port_hint), "dim-label"); + gtk_widget_set_halign(port_hint, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(grid), port_hint, 1, row, 1, 1); + row++; + + // Protocol + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Protocol:"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), proto_combo, 1, row, 1, 1); + row++; + + // Action + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Action:"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), action_combo, 1, row, 1, 1); + row++; + + gtk_container_add(GTK_CONTAINER(content), grid); + + GtkWidget *save_btn = gtk_button_new_with_label("Save"); + GtkWidget *cancel_btn = gtk_button_new_with_label("Cancel"); + gtk_dialog_add_action_widget(GTK_DIALOG(dialog), cancel_btn, GTK_RESPONSE_CANCEL); + gtk_dialog_add_action_widget(GTK_DIALOG(dialog), save_btn, GTK_RESPONSE_ACCEPT); + + // Pass everything needed to callback + GtkWidget **data = malloc(7 * sizeof(GtkWidget*)); + data[0] = dialog; + data[1] = proc_entry; + data[2] = host_entry; + data[3] = port_entry; + data[4] = proto_combo; + data[5] = action_combo; + data[6] = (GtkWidget*)rule; // Hacky cast + + g_signal_connect(save_btn, "clicked", G_CALLBACK(on_save_rule), data); + g_signal_connect(cancel_btn, "clicked", G_CALLBACK(gtk_widget_destroy), NULL); + // Simplified for now. + + gtk_widget_show_all(dialog); +} + +static void on_rule_edit(GtkWidget *widget, gpointer data) { + RuleData *rule = (RuleData *)data; + open_rule_dialog(rule); +} + +static void on_rule_add_clicked(GtkWidget *widget, gpointer data) { + open_rule_dialog(NULL); +} + +static void refresh_rules_ui() { + if (!rules_list_box) return; + + // Clear existing + GList *children, *iter; + children = gtk_container_get_children(GTK_CONTAINER(rules_list_box)); + for(iter = children; iter != NULL; iter = g_list_next(iter)) + gtk_widget_destroy(GTK_WIDGET(iter->data)); + g_list_free(children); + + // Use GtkGrid for alignment + GtkWidget *grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(grid), 10); + gtk_grid_set_column_spacing(GTK_GRID(grid), 15); + gtk_container_add(GTK_CONTAINER(rules_list_box), grid); + + // Headers + // Col 0: Enabled (small) + // Col 1: Actions (medium) + // Col 2: SR (small) + // Col 3: Process (expand) + // Col 4: Host (expand) + // Col 5: Protocol (fixed) + // Col 6: Action (fixed) + + int row = 0; + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("En"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Actions"), 1, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("SR"), 2, row, 1, 1); + + GtkWidget *h_proc = gtk_label_new("Process"); gtk_widget_set_halign(h_proc, GTK_ALIGN_START); + gtk_widget_set_hexpand(h_proc, TRUE); + gtk_grid_attach(GTK_GRID(grid), h_proc, 3, row, 1, 1); + + GtkWidget *h_host = gtk_label_new("Target Hosts"); gtk_widget_set_halign(h_host, GTK_ALIGN_START); + gtk_widget_set_hexpand(h_host, TRUE); + gtk_grid_attach(GTK_GRID(grid), h_host, 4, row, 1, 1); + + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Protocol"), 5, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Action"), 6, row, 1, 1); + + // Separator + row++; + gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 7, 1); + row++; + + // Data Rows + int sr_counter = 1; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *r = (RuleData *)l->data; + + // Enabled + GtkWidget *chk = gtk_check_button_new(); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk), r->enabled); + g_signal_connect(chk, "toggled", G_CALLBACK(on_rule_toggle), r); + gtk_grid_attach(GTK_GRID(grid), chk, 0, row, 1, 1); + + // Actions + GtkWidget *act_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2); + GtkWidget *btn_edit = gtk_button_new_with_label("Edit"); + g_signal_connect(btn_edit, "clicked", G_CALLBACK(on_rule_edit), r); + GtkWidget *btn_del = gtk_button_new_with_label("Delete"); + g_signal_connect(btn_del, "clicked", G_CALLBACK(on_rule_delete), r); + gtk_box_pack_start(GTK_BOX(act_box), btn_edit, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(act_box), btn_del, FALSE, FALSE, 0); + gtk_grid_attach(GTK_GRID(grid), act_box, 1, row, 1, 1); + + // SR + char sr_str[8]; sprintf(sr_str, "%d", sr_counter++); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new(sr_str), 2, row, 1, 1); + + // Process + GtkWidget *l_proc = gtk_label_new(r->process_name); + gtk_widget_set_halign(l_proc, GTK_ALIGN_START); + gtk_label_set_ellipsize(GTK_LABEL(l_proc), PANGO_ELLIPSIZE_END); + gtk_grid_attach(GTK_GRID(grid), l_proc, 3, row, 1, 1); + + // Host + GtkWidget *l_host = gtk_label_new(r->target_hosts); + gtk_widget_set_halign(l_host, GTK_ALIGN_START); + gtk_label_set_ellipsize(GTK_LABEL(l_host), PANGO_ELLIPSIZE_END); + gtk_grid_attach(GTK_GRID(grid), l_host, 4, row, 1, 1); + + // Protocol + const char* proto_strs[] = {"TCP", "UDP", "BOTH"}; + gtk_grid_attach(GTK_GRID(grid), gtk_label_new(proto_strs[r->protocol]), 5, row, 1, 1); + + // Action + const char* action_strs[] = {"PROXY", "DIRECT", "BLOCK"}; + GtkWidget *l_act = gtk_label_new(action_strs[r->action]); + // Set Color + GtkStyleContext *context = gtk_widget_get_style_context(l_act); + if (r->action == RULE_ACTION_PROXY) gtk_style_context_add_class(context, "success"); + else if (r->action == RULE_ACTION_BLOCK) gtk_style_context_add_class(context, "error"); + else gtk_style_context_add_class(context, "warning"); + + gtk_grid_attach(GTK_GRID(grid), l_act, 6, row, 1, 1); + + row++; + gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 7, 1); + row++; + } + + gtk_widget_show_all(rules_list_box); +} + +static void on_proxy_rules_clicked(GtkWidget *widget, gpointer data) { + GtkWidget *dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(dialog), "Process Rules"); + gtk_window_set_default_size(GTK_WINDOW(dialog), 800, 500); + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window)); + + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 20); + gtk_container_add(GTK_CONTAINER(dialog), vbox); + + // Header Row with Title and Add Button + GtkWidget *header_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + GtkWidget *title = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(title), "Process Rules"); + + GtkWidget *add_btn = gtk_button_new_with_label("+ Add Rule"); + g_signal_connect(add_btn, "clicked", G_CALLBACK(on_rule_add_clicked), NULL); + + gtk_box_pack_start(GTK_BOX(header_box), title, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(header_box), add_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), header_box, FALSE, FALSE, 0); + + // Rules List Area + GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_vexpand(scrolled, TRUE); + rules_list_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + gtk_container_add(GTK_CONTAINER(scrolled), rules_list_box); + gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0); + + refresh_rules_ui(); + + gtk_widget_show_all(dialog); +} + static void on_about(GtkWidget *widget, gpointer data) { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, @@ -506,10 +895,15 @@ int main(int argc, char *argv[]) { GtkWidget *proxy_menu_item = gtk_menu_item_new_with_label("Proxy"); GtkWidget *proxy_menu = gtk_menu_new(); GtkWidget *config_item = gtk_menu_item_new_with_label("Proxy Settings"); + GtkWidget *rules_item = gtk_menu_item_new_with_label("Proxy Rules"); GtkWidget *exit_item = gtk_menu_item_new_with_label("Exit"); + g_signal_connect(config_item, "activate", G_CALLBACK(on_proxy_configure), NULL); + g_signal_connect(rules_item, "activate", G_CALLBACK(on_proxy_rules_clicked), NULL); g_signal_connect(exit_item, "activate", G_CALLBACK(on_window_destroy), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), config_item); + gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), rules_item); gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), gtk_separator_menu_item_new()); gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), exit_item); gtk_menu_item_set_submenu(GTK_MENU_ITEM(proxy_menu_item), proxy_menu); diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index c3bfc74..da0aec0 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -185,7 +185,16 @@ static bool parse_token_list(const char *list, const char *delimiters, token_mat while (token != NULL) { token = skip_whitespace(token); - if (match_func(token, match_data)) + + // Trim trailing whitespace + size_t tlen = strlen(token); + while (tlen > 0 && (token[tlen - 1] == ' ' || token[tlen - 1] == '\t' || token[tlen-1] == '\r' || token[tlen-1] == '\n')) + { + token[tlen - 1] = '\0'; + tlen--; + } + + if (tlen > 0 && match_func(token, match_data)) { matched = true; break; @@ -518,9 +527,21 @@ static bool match_ip_pattern(const char *pattern, uint32_t ip) { if (strcmp(pattern_octets[i], "*") == 0) continue; - int pattern_val = atoi(pattern_octets[i]); - if (pattern_val != ip_octets[i]) - return false; + + char *dash = strchr(pattern_octets[i], '-'); + if (dash != NULL) + { + int start = atoi(pattern_octets[i]); + int end = atoi(dash + 1); + if (ip_octets[i] < start || ip_octets[i] > end) + return false; + } + else + { + int pattern_val = atoi(pattern_octets[i]); + if (pattern_val != ip_octets[i]) + return false; + } } return true; } From 4624010c5e09100bb8a4ec2d201ce082133aab35 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 20:09:00 +0530 Subject: [PATCH 16/58] added rule selecton and delete option --- Linux/gui/main.c | 167 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 142 insertions(+), 25 deletions(-) diff --git a/Linux/gui/main.c b/Linux/gui/main.c index fc3f042..452c446 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -58,6 +58,7 @@ typedef struct { RuleProtocol protocol; RuleAction action; bool enabled; + bool selected; } RuleData; static GList *g_rules_list = NULL; @@ -534,6 +535,7 @@ static void on_save_rule(GtkWidget *widget, gpointer data) { new_rule->protocol = proto; new_rule->action = action; new_rule->enabled = true; // Default enabled + new_rule->selected = false; g_rules_list = g_list_append(g_rules_list, new_rule); } @@ -693,6 +695,80 @@ static void on_rule_add_clicked(GtkWidget *widget, gpointer data) { open_rule_dialog(NULL); } +static GtkWidget *btn_select_all_header = NULL; // Renamed to separate from any local vars + +// Note: rules_list_box is already defined at top of file, so we just use it +static void refresh_rules_ui(); // Forward decl + +static void on_rule_select_toggle(GtkToggleButton *btn, gpointer data) { + RuleData *rule = (RuleData *)data; + rule->selected = gtk_toggle_button_get_active(btn); + // Refresh only the Select All button label + if (btn_select_all_header) { + bool all_selected = (g_rules_list != NULL); + if (g_rules_list == NULL) all_selected = false; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *r = (RuleData *)l->data; + if (!r->selected) { + all_selected = false; + break; + } + } + gtk_button_set_label(GTK_BUTTON(btn_select_all_header), all_selected ? "Deselect All" : "Select All"); + } +} + +static void on_bulk_delete_clicked(GtkWidget *widget, gpointer data) { + if (!g_rules_list) return; + + GList *iter = g_rules_list; + // Collect to-delete items first + GList *to_delete = NULL; + + while (iter != NULL) { + RuleData *rule = (RuleData *)iter->data; + if (rule->selected) { + to_delete = g_list_append(to_delete, rule); + } + iter = iter->next; + } + + if (!to_delete) return; // Nothing to delete + + // Delete them + for (GList *d = to_delete; d != NULL; d = d->next) { + RuleData *rule = (RuleData *)d->data; + ProxyBridge_DeleteRule(rule->id); + g_rules_list = g_list_remove(g_rules_list, rule); + free_rule_data(rule); + } + g_list_free(to_delete); + + refresh_rules_ui(); +} + +static void on_select_all_clicked(GtkWidget *widget, gpointer data) { + if (!g_rules_list) return; + + // Check if currently all selected + bool all_selected = true; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *r = (RuleData *)l->data; + if (!r->selected) { + all_selected = false; + break; + } + } + + bool new_state = !all_selected; // Toggle + + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *r = (RuleData *)l->data; + r->selected = new_state; + } + refresh_rules_ui(); +} + static void refresh_rules_ui() { if (!rules_list_box) return; @@ -702,6 +778,21 @@ static void refresh_rules_ui() { for(iter = children; iter != NULL; iter = g_list_next(iter)) gtk_widget_destroy(GTK_WIDGET(iter->data)); g_list_free(children); + + // Check All status for button label + if (btn_select_all_header) { + bool all_selected = (g_rules_list != NULL); + if (g_rules_list == NULL) all_selected = false; + + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *r = (RuleData *)l->data; + if (!r->selected) { + all_selected = false; + break; + } + } + gtk_button_set_label(GTK_BUTTON(btn_select_all_header), all_selected ? "Deselect All" : "Select All"); + } // Use GtkGrid for alignment GtkWidget *grid = gtk_grid_new(); @@ -710,33 +801,35 @@ static void refresh_rules_ui() { gtk_container_add(GTK_CONTAINER(rules_list_box), grid); // Headers - // Col 0: Enabled (small) - // Col 1: Actions (medium) - // Col 2: SR (small) - // Col 3: Process (expand) - // Col 4: Host (expand) - // Col 5: Protocol (fixed) - // Col 6: Action (fixed) + // Col 0: Select (new) + // Col 1: Enabled + // Col 2: Actions + // Col 3: SR + // Col 4: Process + // Col 5: Host + // Col 6: Protocol + // Col 7: Action int row = 0; - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("En"), 0, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Actions"), 1, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("SR"), 2, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new(" "), 0, row, 1, 1); // Selection Header + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Enable"), 1, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Actions"), 2, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("SR"), 3, row, 1, 1); GtkWidget *h_proc = gtk_label_new("Process"); gtk_widget_set_halign(h_proc, GTK_ALIGN_START); gtk_widget_set_hexpand(h_proc, TRUE); - gtk_grid_attach(GTK_GRID(grid), h_proc, 3, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), h_proc, 4, row, 1, 1); GtkWidget *h_host = gtk_label_new("Target Hosts"); gtk_widget_set_halign(h_host, GTK_ALIGN_START); gtk_widget_set_hexpand(h_host, TRUE); - gtk_grid_attach(GTK_GRID(grid), h_host, 4, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), h_host, 5, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Protocol"), 5, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Action"), 6, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Protocol"), 6, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Action"), 7, row, 1, 1); // Separator row++; - gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 7, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 8, 1); row++; // Data Rows @@ -744,11 +837,17 @@ static void refresh_rules_ui() { for (GList *l = g_rules_list; l != NULL; l = l->next) { RuleData *r = (RuleData *)l->data; + // Select Checkbox + GtkWidget *chk_sel = gtk_check_button_new(); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk_sel), r->selected); + g_signal_connect(chk_sel, "toggled", G_CALLBACK(on_rule_select_toggle), r); + gtk_grid_attach(GTK_GRID(grid), chk_sel, 0, row, 1, 1); + // Enabled GtkWidget *chk = gtk_check_button_new(); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk), r->enabled); g_signal_connect(chk, "toggled", G_CALLBACK(on_rule_toggle), r); - gtk_grid_attach(GTK_GRID(grid), chk, 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), chk, 1, row, 1, 1); // Actions GtkWidget *act_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2); @@ -758,27 +857,27 @@ static void refresh_rules_ui() { g_signal_connect(btn_del, "clicked", G_CALLBACK(on_rule_delete), r); gtk_box_pack_start(GTK_BOX(act_box), btn_edit, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(act_box), btn_del, FALSE, FALSE, 0); - gtk_grid_attach(GTK_GRID(grid), act_box, 1, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), act_box, 2, row, 1, 1); // SR char sr_str[8]; sprintf(sr_str, "%d", sr_counter++); - gtk_grid_attach(GTK_GRID(grid), gtk_label_new(sr_str), 2, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new(sr_str), 3, row, 1, 1); // Process GtkWidget *l_proc = gtk_label_new(r->process_name); gtk_widget_set_halign(l_proc, GTK_ALIGN_START); gtk_label_set_ellipsize(GTK_LABEL(l_proc), PANGO_ELLIPSIZE_END); - gtk_grid_attach(GTK_GRID(grid), l_proc, 3, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), l_proc, 4, row, 1, 1); // Host GtkWidget *l_host = gtk_label_new(r->target_hosts); gtk_widget_set_halign(l_host, GTK_ALIGN_START); gtk_label_set_ellipsize(GTK_LABEL(l_host), PANGO_ELLIPSIZE_END); - gtk_grid_attach(GTK_GRID(grid), l_host, 4, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), l_host, 5, row, 1, 1); // Protocol const char* proto_strs[] = {"TCP", "UDP", "BOTH"}; - gtk_grid_attach(GTK_GRID(grid), gtk_label_new(proto_strs[r->protocol]), 5, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new(proto_strs[r->protocol]), 6, row, 1, 1); // Action const char* action_strs[] = {"PROXY", "DIRECT", "BLOCK"}; @@ -786,13 +885,13 @@ static void refresh_rules_ui() { // Set Color GtkStyleContext *context = gtk_widget_get_style_context(l_act); if (r->action == RULE_ACTION_PROXY) gtk_style_context_add_class(context, "success"); - else if (r->action == RULE_ACTION_BLOCK) gtk_style_context_add_class(context, "error"); + else if (r->action == RULE_ACTION_DIRECT) gtk_style_context_add_class(context, "info"); else gtk_style_context_add_class(context, "warning"); - gtk_grid_attach(GTK_GRID(grid), l_act, 6, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), l_act, 7, row, 1, 1); row++; - gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 7, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 8, 1); row++; } @@ -817,8 +916,26 @@ static void on_proxy_rules_clicked(GtkWidget *widget, gpointer data) { GtkWidget *add_btn = gtk_button_new_with_label("+ Add Rule"); g_signal_connect(add_btn, "clicked", G_CALLBACK(on_rule_add_clicked), NULL); + // Select All Button + btn_select_all_header = gtk_button_new_with_label("Select All"); + g_signal_connect(btn_select_all_header, "clicked", G_CALLBACK(on_select_all_clicked), NULL); + + // Bulk Delete Button + GtkWidget *del_all_btn = gtk_button_new_with_label("Delete Selected"); + g_signal_connect(del_all_btn, "clicked", G_CALLBACK(on_bulk_delete_clicked), NULL); + gtk_box_pack_start(GTK_BOX(header_box), title, FALSE, FALSE, 0); - gtk_box_pack_end(GTK_BOX(header_box), add_btn, FALSE, FALSE, 0); + + // Spacing + GtkWidget *spacer = gtk_label_new(""); + gtk_widget_set_hexpand(spacer, TRUE); + gtk_box_pack_start(GTK_BOX(header_box), spacer, TRUE, TRUE, 0); + + // Buttons + gtk_box_pack_end(GTK_BOX(header_box), add_btn, FALSE, FALSE, 5); + gtk_box_pack_end(GTK_BOX(header_box), btn_select_all_header, FALSE, FALSE, 5); + gtk_box_pack_end(GTK_BOX(header_box), del_all_btn, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(vbox), header_box, FALSE, FALSE, 0); // Rules List Area From 3413b47278cf09fce567ae744b9623944e220ba5 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 20:52:11 +0530 Subject: [PATCH 17/58] added proxy rule import export --- Linux/gui/main.c | 203 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 202 insertions(+), 1 deletion(-) diff --git a/Linux/gui/main.c b/Linux/gui/main.c index 452c446..c1fb3ef 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -718,6 +718,194 @@ static void on_rule_select_toggle(GtkToggleButton *btn, gpointer data) { } } +// --- JSON Helpers --- +static char *escape_json_string(const char *src) { + if (!src) return strdup(""); + GString *str = g_string_new(""); + for (const char *p = src; *p; p++) { + if (*p == '\\') g_string_append(str, "\\\\"); + else if (*p == '"') g_string_append(str, "\\\""); + else if (*p == '\n') g_string_append(str, "\\n"); + else g_string_append_c(str, *p); + } + return g_string_free(str, FALSE); +} + +// Very basic JSON parser for valid input +static char *extract_sub_json_str(const char *json, const char *key) { + char search_key[256]; + snprintf(search_key, sizeof(search_key), "\"%s\"", key); + char *k = strstr(json, search_key); + if (!k) return NULL; + char *colon = strchr(k, ':'); + if (!colon) return NULL; + char *val_start = strchr(colon, '"'); + if (!val_start) return NULL; + val_start++; + char *val_end = strchr(val_start, '"'); + if (!val_end) return NULL; + return g_strndup(val_start, val_end - val_start); +} + +static bool extract_sub_json_bool(const char *json, const char *key) { + char search_key[256]; + snprintf(search_key, sizeof(search_key), "\"%s\"", key); + char *k = strstr(json, search_key); + if (!k) return false; + char *colon = strchr(k, ':'); + if (!colon) return false; + // Skip spaces + char *v = colon + 1; + while(*v == ' ' || *v == '\t') v++; + if (strncmp(v, "true", 4) == 0) return true; + return false; +} + +static void on_rule_export_clicked(GtkWidget *widget, gpointer data) { + if (!g_rules_list) return; + + // Check if any selected + bool any_selected = false; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + if (((RuleData *)l->data)->selected) { any_selected = true; break; } + } + if (!any_selected) { + GtkWidget *msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Please select at least one rule to export."); + gtk_dialog_run(GTK_DIALOG(msg)); + gtk_widget_destroy(msg); + return; + } + + GtkWidget *dialog = gtk_file_chooser_dialog_new("Export Rules", + GTK_WINDOW(window), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "proxy_rules.json"); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + FILE *f = fopen(filename, "w"); + if (f) { + fprintf(f, "[\n"); + bool first = true; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *r = (RuleData *)l->data; + if (!r->selected) continue; + + if (!first) fprintf(f, ",\n"); + char *proc = escape_json_string(r->process_name); + char *host = escape_json_string(r->target_hosts); + char *port = escape_json_string(r->target_ports); + const char *proto = (r->protocol == RULE_PROTOCOL_TCP) ? "TCP" : (r->protocol == RULE_PROTOCOL_UDP ? "UDP" : "BOTH"); + const char *act = (r->action == RULE_ACTION_PROXY) ? "PROXY" : (r->action == RULE_ACTION_DIRECT ? "DIRECT" : "BLOCK"); + + fprintf(f, " {\n"); + fprintf(f, " \"processNames\": \"%s\",\n", proc); + fprintf(f, " \"targetHosts\": \"%s\",\n", host); + fprintf(f, " \"targetPorts\": \"%s\",\n", port); + fprintf(f, " \"protocol\": \"%s\",\n", proto); + fprintf(f, " \"action\": \"%s\",\n", act); + fprintf(f, " \"enabled\": %s\n", r->enabled ? "true" : "false"); + fprintf(f, " }"); + + g_free(proc); g_free(host); g_free(port); + first = false; + } + fprintf(f, "\n]\n"); + fclose(f); + + GtkWidget *msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "Rules exported successfully."); + gtk_dialog_run(GTK_DIALOG(msg)); + gtk_widget_destroy(msg); + } + g_free(filename); + } + gtk_widget_destroy(dialog); +} + +static void on_rule_import_clicked(GtkWidget *widget, gpointer data) { + GtkWidget *dialog = gtk_file_chooser_dialog_new("Import Rules", + GTK_WINDOW(window), + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + NULL); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + char *content = NULL; + gsize len; + + if (g_file_get_contents(filename, &content, &len, NULL)) { + // Simple robust scan: look for { ... } blocks + char *curr = content; + int imported = 0; + while ((curr = strchr(curr, '{')) != NULL) { + char *end = strchr(curr, '}'); + if (!end) break; + + // Temp terminate to limit search scope + char saved = *end; + *end = '\0'; + + // Parse + char *proc = extract_sub_json_str(curr, "processNames"); + char *host = extract_sub_json_str(curr, "targetHosts"); + char *port = extract_sub_json_str(curr, "targetPorts"); + char *proto_s = extract_sub_json_str(curr, "protocol"); + char *act_s = extract_sub_json_str(curr, "action"); + bool en = extract_sub_json_bool(curr, "enabled"); + + if (proc && host && port && proto_s && act_s) { + RuleProtocol p = RULE_PROTOCOL_BOTH; + if (strcmp(proto_s, "TCP") == 0) p = RULE_PROTOCOL_TCP; + else if (strcmp(proto_s, "UDP") == 0) p = RULE_PROTOCOL_UDP; + + RuleAction a = RULE_ACTION_PROXY; + if (strcmp(act_s, "DIRECT") == 0) a = RULE_ACTION_DIRECT; + else if (strcmp(act_s, "BLOCK") == 0) a = RULE_ACTION_BLOCK; + + uint32_t nid = ProxyBridge_AddRule(proc, host, port, p, a); + // Update enabled if needed (AddRule creates enabled by default, but check ID) + if (!en) ProxyBridge_DisableRule(nid); + + // Add to UI list struct + RuleData *nd = malloc(sizeof(RuleData)); + nd->id = nid; + nd->process_name = strdup(proc); + nd->target_hosts = strdup(host); + nd->target_ports = strdup(port); + nd->protocol = p; + nd->action = a; + nd->enabled = en; + nd->selected = false; + + g_rules_list = g_list_append(g_rules_list, nd); + imported++; + } + + g_free(proc); g_free(host); g_free(port); g_free(proto_s); g_free(act_s); + + *end = saved; // Restore + curr = end + 1; + } + g_free(content); + + if (imported > 0) { + refresh_rules_ui(); + GtkWidget *msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "Imported %d rules.", imported); + gtk_dialog_run(GTK_DIALOG(msg)); + gtk_widget_destroy(msg); + } + } + g_free(filename); + } + gtk_widget_destroy(dialog); +} + static void on_bulk_delete_clicked(GtkWidget *widget, gpointer data) { if (!g_rules_list) return; @@ -924,6 +1112,13 @@ static void on_proxy_rules_clicked(GtkWidget *widget, gpointer data) { GtkWidget *del_all_btn = gtk_button_new_with_label("Delete Selected"); g_signal_connect(del_all_btn, "clicked", G_CALLBACK(on_bulk_delete_clicked), NULL); + // Import/Export Buttons + GtkWidget *import_btn = gtk_button_new_with_label("Import"); + g_signal_connect(import_btn, "clicked", G_CALLBACK(on_rule_import_clicked), NULL); + + GtkWidget *export_btn = gtk_button_new_with_label("Export"); + g_signal_connect(export_btn, "clicked", G_CALLBACK(on_rule_export_clicked), NULL); + gtk_box_pack_start(GTK_BOX(header_box), title, FALSE, FALSE, 0); // Spacing @@ -931,10 +1126,12 @@ static void on_proxy_rules_clicked(GtkWidget *widget, gpointer data) { gtk_widget_set_hexpand(spacer, TRUE); gtk_box_pack_start(GTK_BOX(header_box), spacer, TRUE, TRUE, 0); - // Buttons + // Buttons (Packed End = Right to Left order on screen) gtk_box_pack_end(GTK_BOX(header_box), add_btn, FALSE, FALSE, 5); gtk_box_pack_end(GTK_BOX(header_box), btn_select_all_header, FALSE, FALSE, 5); gtk_box_pack_end(GTK_BOX(header_box), del_all_btn, FALSE, FALSE, 5); + gtk_box_pack_end(GTK_BOX(header_box), export_btn, FALSE, FALSE, 5); + gtk_box_pack_end(GTK_BOX(header_box), import_btn, FALSE, FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), header_box, FALSE, FALSE, 0); @@ -990,6 +1187,10 @@ int main(int argc, char *argv[]) { return 1; } + // Force GSettings backend to 'memory' to prevent dconf/dbus-launch errors when running as root + // This suppresses "failed to commit changes to dconf" warnings. + setenv("GSETTINGS_BACKEND", "memory", 1); + gtk_init(&argc, &argv); // Apply dark theme preference if available From 0c4d0f8f0e1fc5942879118d183e03833a71463f Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 21:52:18 +0530 Subject: [PATCH 18/58] added update checker, proxy traffic, dns fixed, updated GUI, fixed crash --- Linux/gui/main.c | 149 ++++++++++++++++++++++++++++++++++++++-- Linux/src/ProxyBridge.c | 18 ++--- 2 files changed, 149 insertions(+), 18 deletions(-) diff --git a/Linux/gui/main.c b/Linux/gui/main.c index c1fb3ef..72e8530 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -1147,16 +1148,128 @@ static void on_proxy_rules_clicked(GtkWidget *widget, gpointer data) { gtk_widget_show_all(dialog); } +static void on_log_traffic_toggled(GtkCheckMenuItem *item, gpointer data) { + bool active = gtk_check_menu_item_get_active(item); + ProxyBridge_SetTrafficLoggingEnabled(active); +} + +static void on_dns_proxy_toggled(GtkCheckMenuItem *item, gpointer data) { + bool active = gtk_check_menu_item_get_active(item); + ProxyBridge_SetDnsViaProxy(active); +} + static void on_about(GtkWidget *widget, gpointer data) { - GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_INFO, - GTK_BUTTONS_OK, - "ProxyBridge Linux GUI\nVersion 4.0-Beta\n\nHigh-performance zero-copy proxy client."); + GtkWidget *dialog = gtk_dialog_new_with_buttons("About ProxyBridge", + GTK_WINDOW(window), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + "OK", GTK_RESPONSE_OK, + NULL); + gtk_window_set_default_size(GTK_WINDOW(dialog), 400, 300); + + GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_set_border_width(GTK_CONTAINER(content_area), 20); + + const char *markup = + "ProxyBridge\n" + "Version 4.0-Beta\n\n" + "Universal proxy client for Linux applications\n\n" + "Author: Sourav Kalal / InterceptSuite\n\n" + "Website: interceptsuite.com\n" + "GitHub: InterceptSuite/ProxyBridge\n\n" + "License: MIT"; + + GtkWidget *label = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(label), markup); + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER); + + gtk_box_pack_start(GTK_BOX(content_area), label, TRUE, TRUE, 0); + gtk_widget_show_all(dialog); + gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } +static void on_create_update_script_and_run() { + // We use execl to replace the current process, so we must stop everything first + ProxyBridge_Stop(); + + const char *script_url = "https://raw.githubusercontent.com/InterceptSuite/ProxyBridge/refs/heads/master/Linux/deploy.sh"; + const char *script_path = "/tmp/proxybridge_deploy.sh"; + + char dl_cmd[512]; + snprintf(dl_cmd, sizeof(dl_cmd), "curl -s -o %s %s && chmod +x %s", script_path, script_url, script_path); + + if (system(dl_cmd) != 0) { + // This likely won't be seen if we are shutting down, but worth a try (or stderr) + fprintf(stderr, "Failed to download update script.\n"); + exit(1); + } + + // Replace process with the update script + execl("/bin/bash", "bash", script_path, NULL); + exit(0); // Should not reach here +} + +static void on_check_update(GtkWidget *widget, gpointer data) { + const char *url = "https://api.github.com/repos/InterceptSuite/ProxyBridge/releases/latest"; + char cmd[512]; + snprintf(cmd, sizeof(cmd), "curl -s -H \"User-Agent: ProxyBridge-Linux\" %s", url); + + FILE *fp = popen(cmd, "r"); + if (!fp) { + GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Failed to contact update server."); + gtk_dialog_run(GTK_DIALOG(msg)); + gtk_widget_destroy(msg); + return; + } + + char buffer[4096]; + size_t n = fread(buffer, 1, sizeof(buffer)-1, fp); + buffer[n] = 0; + pclose(fp); + + if (n == 0) { + GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Empty response from update server."); + gtk_dialog_run(GTK_DIALOG(msg)); + gtk_widget_destroy(msg); + return; + } + + char *tag_name = extract_sub_json_str(buffer, "tag_name"); + if (!tag_name) { + GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Could not parse version info.\nResponse might be rate limited."); + gtk_dialog_run(GTK_DIALOG(msg)); + gtk_widget_destroy(msg); + return; + } + + // Compare + const char *current_ver = "v4.0.0"; + + if (strcmp(tag_name, current_ver) == 0) { + GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "You are using the latest version (%s).", current_ver); + gtk_dialog_run(GTK_DIALOG(msg)); + gtk_widget_destroy(msg); + } else { + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + "New version %s is available!\nCurrent version: %s\n\nDo you want to update now?", + tag_name, current_ver); + gtk_dialog_add_button(GTK_DIALOG(dialog), "Download Now", GTK_RESPONSE_ACCEPT); + gtk_dialog_add_button(GTK_DIALOG(dialog), "Close", GTK_RESPONSE_CANCEL); + + int resp = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + if (resp == GTK_RESPONSE_ACCEPT) { + on_create_update_script_and_run(); + } + } + g_free(tag_name); +} + static void signal_handler(int sig) { fprintf(stderr, "\nSignal %d received. Stopping ProxyBridge...\n", sig); ProxyBridge_Stop(); @@ -1214,6 +1327,16 @@ int main(int argc, char *argv[]) { GtkWidget *proxy_menu = gtk_menu_new(); GtkWidget *config_item = gtk_menu_item_new_with_label("Proxy Settings"); GtkWidget *rules_item = gtk_menu_item_new_with_label("Proxy Rules"); + + // New Check Menu Items + GtkWidget *log_check_item = gtk_check_menu_item_new_with_label("Enable Traffic Logging"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(log_check_item), TRUE); // Default + g_signal_connect(log_check_item, "toggled", G_CALLBACK(on_log_traffic_toggled), NULL); + + GtkWidget *dns_check_item = gtk_check_menu_item_new_with_label("DNS via Proxy"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(dns_check_item), TRUE); // Default + g_signal_connect(dns_check_item, "toggled", G_CALLBACK(on_dns_proxy_toggled), NULL); + GtkWidget *exit_item = gtk_menu_item_new_with_label("Exit"); g_signal_connect(config_item, "activate", G_CALLBACK(on_proxy_configure), NULL); @@ -1223,11 +1346,25 @@ int main(int argc, char *argv[]) { gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), config_item); gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), rules_item); gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), gtk_separator_menu_item_new()); + gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), log_check_item); + gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), dns_check_item); + gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), gtk_separator_menu_item_new()); gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), exit_item); gtk_menu_item_set_submenu(GTK_MENU_ITEM(proxy_menu_item), proxy_menu); GtkWidget *about_menu_item = gtk_menu_item_new_with_label("About"); - g_signal_connect(about_menu_item, "activate", G_CALLBACK(on_about), NULL); + GtkWidget *about_menu = gtk_menu_new(); + + GtkWidget *about_child_item = gtk_menu_item_new_with_label("About"); + g_signal_connect(about_child_item, "activate", G_CALLBACK(on_about), NULL); + + GtkWidget *update_item = gtk_menu_item_new_with_label("Check for Updates"); + g_signal_connect(update_item, "activate", G_CALLBACK(on_check_update), NULL); + + gtk_menu_shell_append(GTK_MENU_SHELL(about_menu), about_child_item); + gtk_menu_shell_append(GTK_MENU_SHELL(about_menu), update_item); + + gtk_menu_item_set_submenu(GTK_MENU_ITEM(about_menu_item), about_menu); gtk_menu_shell_append(GTK_MENU_SHELL(menubar), proxy_menu_item); gtk_menu_shell_append(GTK_MENU_SHELL(menubar), about_menu_item); diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index da0aec0..829b3c7 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -1771,7 +1771,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru action = RULE_ACTION_DIRECT; // log connection (skip our own process) - if (g_connection_callback != NULL && (tcph->syn && !tcph->ack) && pid > 0 && pid != g_current_process_id) + if (g_traffic_logging_enabled && g_connection_callback != NULL && (tcph->syn && !tcph->ack) && pid > 0 && pid != g_current_process_id) { char process_name[MAX_PROCESS_NAME]; if (get_process_name_from_pid(pid, process_name, sizeof(process_name))) @@ -1799,11 +1799,8 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru const char* display_name = extract_filename(process_name); g_connection_callback(display_name, pid, dest_ip_str, dest_port, proxy_info); - - if (g_traffic_logging_enabled) - { - add_logged_connection(pid, dest_ip, dest_port, action); - } + + add_logged_connection(pid, dest_ip, dest_port, action); } } } @@ -1853,7 +1850,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru action = RULE_ACTION_DIRECT; // log (skip our own process, log even without PID for ephemeral UDP sockets) - if (g_connection_callback != NULL && pid != g_current_process_id) + if (g_traffic_logging_enabled && g_connection_callback != NULL && pid != g_current_process_id) { char process_name[MAX_PROCESS_NAME]; uint32_t log_pid = (pid == 0) ? 1 : pid; // Use PID 1 for unknown processes @@ -1890,11 +1887,8 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru const char* display_name = extract_filename(process_name); g_connection_callback(display_name, log_pid, dest_ip_str, dest_port, proxy_info); - - if (g_traffic_logging_enabled) - { - add_logged_connection(log_pid, dest_ip, dest_port, action); - } + + add_logged_connection(log_pid, dest_ip, dest_port, action); } } From f750ecdc32f6a38c088a86e80e0d479c9f8c45b6 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 22:38:50 +0530 Subject: [PATCH 19/58] optimized iptable commands, GUI, gui compiler --- Linux/build.sh | 24 +++++---- Linux/gui/main.c | 106 ++++++++++++++++++++++++++++++---------- Linux/src/ProxyBridge.c | 101 +++++++++++++++++++++++++++++++------- 3 files changed, 175 insertions(+), 56 deletions(-) diff --git a/Linux/build.sh b/Linux/build.sh index ed07efc..7d8c800 100755 --- a/Linux/build.sh +++ b/Linux/build.sh @@ -22,9 +22,9 @@ make clean 2>/dev/null || true make if [ -f "libproxybridge.so" ]; then - echo "✓ Library build successful" + echo "Library build successful" else - echo "✗ Library build failed!" + echo "Library build failed!" exit 1 fi echo "" @@ -36,9 +36,9 @@ make clean 2>/dev/null || true make if [ -f "ProxyBridge" ]; then - echo "✓ CLI build successful" + echo "CLI build successful" else - echo "✗ CLI build failed!" + echo "CLI build failed!" exit 1 fi echo "" @@ -48,12 +48,16 @@ echo "=== Building GUI ===" cd "$SCRIPT_DIR" rm -f ProxyBridgeGUI if pkg-config --exists gtk+-3.0; then - gcc -Wall -Wno-unused-parameter -O3 -Isrc -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security -Werror=format-security -fno-strict-overflow -fno-delete-null-pointer-checks -fwrapv -c gui/main.c -o gui_main.o $(pkg-config --cflags gtk+-3.0) - gcc -o ProxyBridgeGUI gui_main.o -Lsrc -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s -Wl,-rpath,'$ORIGIN/.' -lproxybridge -lpthread $(pkg-config --libs gtk+-3.0) -export-dynamic + GUI_CFLAGS="-Wall -Wno-unused-parameter -O3 -Isrc -D_GNU_SOURCE -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security -Werror=format-security -fno-strict-overflow -fno-delete-null-pointer-checks -fwrapv $(pkg-config --cflags gtk+-3.0)" + GUI_LDFLAGS="-Lsrc -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s -Wl,-rpath,'$ORIGIN/.' -lproxybridge -lpthread $(pkg-config --libs gtk+-3.0) -export-dynamic" + + gcc $GUI_CFLAGS -c gui/main.c -o gui_main.o + gcc -o ProxyBridgeGUI gui_main.o $GUI_LDFLAGS + rm -f gui_main.o - echo "✓ GUI build successful" + echo "GUI build successful" else - echo "⚠ GTK3 not found. Skipping GUI build." + echo "GTK3 not found. Skipping GUI build." echo " Install with: sudo apt install libgtk-3-dev (Debian/Ubuntu/Mint)" echo " sudo dnf install gtk3-devel (Fedora)" fi @@ -65,7 +69,7 @@ mv "$SCRIPT_DIR/cli/ProxyBridge" "$OUTPUT_DIR/" if [ -f ProxyBridgeGUI ]; then mv ProxyBridgeGUI "$OUTPUT_DIR/" fi -echo "✓ Binaries moved to output" +echo "Binaries moved to output" echo "" # Cleanup build files @@ -76,7 +80,7 @@ make clean 2>/dev/null || true cd "$SCRIPT_DIR/cli" rm -f *.o make clean 2>/dev/null || true -echo "✓ Cleanup complete" +echo "Cleanup complete" echo "" # Show results diff --git a/Linux/gui/main.c b/Linux/gui/main.c index 72e8530..eec5c9a 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -6,8 +6,20 @@ #include #include #include +#include +#include +#include #include "ProxyBridge.h" +static long safe_strtol(const char *nptr) { + if (!nptr) return 0; // Handle NULL + char *endptr; + long val = strtol(nptr, &endptr, 10); + // You could check errno here if needed, but for GUI fields 0 is often a safe fallback or filtered before + if (endptr == nptr) return 0; // No digits found + return val; +} + // --- Global UI Widgets --- static GtkWidget *window; static GtkTextView *conn_view; @@ -290,7 +302,7 @@ static void on_start_test_clicked(GtkWidget *widget, gpointer data) { // 2. Set Proxy Config ProxyType type = (gtk_combo_box_get_active(GTK_COMBO_BOX(info->type_combo)) == 0) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; - int port = atoi(port_text); + int port = (int)safe_strtol(port_text); const char *user = gtk_entry_get_text(GTK_ENTRY(info->user_entry)); const char *pass = gtk_entry_get_text(GTK_ENTRY(info->pass_entry)); @@ -301,7 +313,7 @@ static void on_start_test_clicked(GtkWidget *widget, gpointer data) { const char *t_port_s = gtk_entry_get_text(GTK_ENTRY(info->test_port)); if (!t_host || strlen(t_host) == 0) t_host = "google.com"; - int t_port = atoi(t_port_s); + int t_port = (int)safe_strtol(t_port_s); if (t_port <= 0) t_port = 80; // 4. Update UI @@ -362,8 +374,8 @@ static void on_proxy_configure(GtkWidget *widget, gpointer data) { gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Port:"), 0, 2, 1, 1); info.port_entry = gtk_entry_new(); if (g_proxy_port != 0) { - char port_str[8]; - sprintf(port_str, "%d", g_proxy_port); + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%d", g_proxy_port); gtk_entry_set_text(GTK_ENTRY(info.port_entry), port_str); } gtk_grid_attach(GTK_GRID(grid), info.port_entry, 1, 2, 3, 1); @@ -444,7 +456,7 @@ static void on_proxy_configure(GtkWidget *widget, gpointer data) { continue; } - int p = atoi(port_text); + int p = (int)safe_strtol(port_text); if (p < 1 || p > 65535) { GtkWidget *err = gtk_message_dialog_new(GTK_WINDOW(info.dialog), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Port must be between 1 and 65535."); gtk_dialog_run(GTK_DIALOG(err)); @@ -1049,7 +1061,7 @@ static void refresh_rules_ui() { gtk_grid_attach(GTK_GRID(grid), act_box, 2, row, 1, 1); // SR - char sr_str[8]; sprintf(sr_str, "%d", sr_counter++); + char sr_str[16]; snprintf(sr_str, sizeof(sr_str), "%d", sr_counter++); gtk_grid_attach(GTK_GRID(grid), gtk_label_new(sr_str), 3, row, 1, 1); // Process @@ -1194,14 +1206,40 @@ static void on_create_update_script_and_run() { ProxyBridge_Stop(); const char *script_url = "https://raw.githubusercontent.com/InterceptSuite/ProxyBridge/refs/heads/master/Linux/deploy.sh"; - const char *script_path = "/tmp/proxybridge_deploy.sh"; - char dl_cmd[512]; - snprintf(dl_cmd, sizeof(dl_cmd), "curl -s -o %s %s && chmod +x %s", script_path, script_url, script_path); + // Secure temp directory + char tmp_dir_tpl[] = "/tmp/pb_update_XXXXXX"; + char *tmp_dir = mkdtemp(tmp_dir_tpl); + if (!tmp_dir) { + fprintf(stderr, "Failed to create temp directory for update.\n"); + exit(1); + } + + char script_path[512]; + snprintf(script_path, sizeof(script_path), "%s/deploy.sh", tmp_dir); - if (system(dl_cmd) != 0) { - // This likely won't be seen if we are shutting down, but worth a try (or stderr) - fprintf(stderr, "Failed to download update script.\n"); + // Download using fork/exec of curl, safer than system() + pid_t pid = fork(); + if (pid == -1) { + fprintf(stderr, "Fork failed.\n"); + exit(1); + } else if (pid == 0) { + // Child + execlp("curl", "curl", "-s", "-o", script_path, script_url, NULL); + _exit(127); + } else { + // Parent + int status; + waitpid(pid, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + fprintf(stderr, "Failed to download update script.\n"); + exit(1); + } + } + + // chmod +x + if (chmod(script_path, S_IRWXU) != 0) { + perror("chmod failed"); exit(1); } @@ -1212,30 +1250,44 @@ static void on_create_update_script_and_run() { static void on_check_update(GtkWidget *widget, gpointer data) { const char *url = "https://api.github.com/repos/InterceptSuite/ProxyBridge/releases/latest"; - char cmd[512]; - snprintf(cmd, sizeof(cmd), "curl -s -H \"User-Agent: ProxyBridge-Linux\" %s", url); - - FILE *fp = popen(cmd, "r"); - if (!fp) { - GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Failed to contact update server."); + char *cmd = g_strdup_printf("curl -s -H \"User-Agent: ProxyBridge-Linux\" %s", url); + + char *standard_output = NULL; + char *standard_error = NULL; + GError *error = NULL; + int exit_status = 0; + + // Use GLib spawn instead of popen for safety and correct signal handling + gboolean result = g_spawn_command_line_sync(cmd, + &standard_output, + &standard_error, + &exit_status, + &error); + g_free(cmd); + + if (!result) { + GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Failed to launch update check: %s", error ? error->message : "Unknown error"); gtk_dialog_run(GTK_DIALOG(msg)); gtk_widget_destroy(msg); + if (error) g_error_free(error); return; } - - char buffer[4096]; - size_t n = fread(buffer, 1, sizeof(buffer)-1, fp); - buffer[n] = 0; - pclose(fp); - - if (n == 0) { - GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Empty response from update server."); + + if (exit_status != 0 || !standard_output || strlen(standard_output) == 0) { + GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Update check failed (Exit: %d).", exit_status); gtk_dialog_run(GTK_DIALOG(msg)); gtk_widget_destroy(msg); + g_free(standard_output); + g_free(standard_error); return; } - char *tag_name = extract_sub_json_str(buffer, "tag_name"); + // Copy to buffer for existing logic (simplified) + // Or just use standard_output directly + char *tag_name = extract_sub_json_str(standard_output, "tag_name"); + g_free(standard_output); + g_free(standard_error); + if (!tag_name) { GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Could not parse version info.\nResponse might be rate limited."); gtk_dialog_run(GTK_DIALOG(msg)); diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index 829b3c7..9b4cad0 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "ProxyBridge.h" #define LOCAL_PROXY_PORT 34010 @@ -38,6 +39,68 @@ #define HTTP_BUFFER_SIZE 1024 #define LOG_BUFFER_SIZE 1024 +// Secure replacement for system() to avoid shell injection and adhere to secure coding practices +static int run_command_v(const char *cmd_path, char *const argv[]) { + pid_t pid = fork(); + if (pid == -1) { + return -1; + } else if (pid == 0) { + // Child + // Redirect stdout/stderr to /dev/null + int fd = open("/dev/null", O_WRONLY); + if (fd >= 0) { + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + execvp(cmd_path, argv); + _exit(127); // Access denied or not found + } else { + // Parent + int status; + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + return -1; + } +} + +// Helper to run simple commands without arguments or simplified interface +static int run_iptables_cmd(const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *arg5, const char *arg6, const char *arg7, const char *arg8, const char *arg9, const char *arg10, const char *arg11, const char *arg12, const char *arg13, const char *arg14) { + // Construct argv excluding NULLs + const char *argv[17]; + int i = 0; + argv[i++] = "iptables"; + if (arg1) argv[i++] = arg1; + if (arg2) argv[i++] = arg2; + if (arg3) argv[i++] = arg3; + if (arg4) argv[i++] = arg4; + if (arg5) argv[i++] = arg5; + if (arg6) argv[i++] = arg6; + if (arg7) argv[i++] = arg7; + if (arg8) argv[i++] = arg8; + if (arg9) argv[i++] = arg9; + if (arg10) argv[i++] = arg10; + if (arg11) argv[i++] = arg11; + if (arg12) argv[i++] = arg12; + if (arg13) argv[i++] = arg13; + if (arg14) argv[i++] = arg14; + argv[i] = NULL; + + return run_command_v("iptables", (char **)argv); +} + +// Helper for strtol to replace atoi +static int safe_atoi(const char *str) { + if (!str) return 0; + char *endptr; + long val = strtol(str, &endptr, 10); + if (endptr == str) return 0; + return (int)val; +} + + typedef struct PROCESS_RULE { uint32_t rule_id; char process_name[MAX_PROCESS_NAME]; @@ -324,7 +387,7 @@ static uint32_t find_pid_from_inode(unsigned long target_inode, uint32_t uid_hin if (link_len == expected_len) { link_target[link_len] = '\0'; if (memcmp(link_target, expected, expected_len) == 0) { - pid = (uint32_t)atoi(proc_entry->d_name); + pid = (uint32_t)safe_atoi(proc_entry->d_name); closedir(fd_dir); closedir(proc_dir); return pid; @@ -531,14 +594,14 @@ static bool match_ip_pattern(const char *pattern, uint32_t ip) char *dash = strchr(pattern_octets[i], '-'); if (dash != NULL) { - int start = atoi(pattern_octets[i]); - int end = atoi(dash + 1); + int start = safe_atoi(pattern_octets[i]); + int end = safe_atoi(dash + 1); if (ip_octets[i] < start || ip_octets[i] > end) return false; } else { - int pattern_val = atoi(pattern_octets[i]); + int pattern_val = safe_atoi(pattern_octets[i]); if (pattern_val != ip_octets[i]) return false; } @@ -554,12 +617,12 @@ static bool match_port_pattern(const char *pattern, uint16_t port) char *dash = strchr(pattern, '-'); if (dash != NULL) { - int start_port = atoi(pattern); - int end_port = atoi(dash + 1); + int start_port = safe_atoi(pattern); + int end_port = safe_atoi(dash + 1); return (port >= start_port && port <= end_port); } - return (port == atoi(pattern)); + return (port == safe_atoi(pattern)); } static bool ip_match_wrapper(const char *token, const void *data) @@ -2673,8 +2736,8 @@ bool ProxyBridge_Start(void) // setup iptables rules for packet interception - USE MANGLE table so it runs BEFORE nat log_message("setting up iptables rules"); // mangle table runs before nat, so we can mark packets there - int ret1 = system("iptables -t mangle -A OUTPUT -p tcp -j NFQUEUE --queue-num 0"); - int ret2 = system("iptables -t mangle -A OUTPUT -p udp -j NFQUEUE --queue-num 0"); + int ret1 = run_iptables_cmd("-t", "mangle", "-A", "OUTPUT", "-p", "tcp", "-j", "NFQUEUE", "--queue-num", "0", NULL, NULL, NULL, NULL); + int ret2 = run_iptables_cmd("-t", "mangle", "-A", "OUTPUT", "-p", "udp", "-j", "NFQUEUE", "--queue-num", "0", NULL, NULL, NULL, NULL); if (ret1 != 0 || ret2 != 0) { log_message("failed to add iptables rules ret1=%d ret2=%d", ret1, ret2); @@ -2683,8 +2746,8 @@ bool ProxyBridge_Start(void) } // setup nat redirect for marked packets - int ret3 = system("iptables -t nat -A OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010"); - int ret4 = system("iptables -t nat -A OUTPUT -p udp -m mark --mark 2 -j REDIRECT --to-port 34011"); + int ret3 = run_iptables_cmd("-t", "nat", "-A", "OUTPUT", "-p", "tcp", "-m", "mark", "--mark", "1", "-j", "REDIRECT", "--to-port", "34010"); + int ret4 = run_iptables_cmd("-t", "nat", "-A", "OUTPUT", "-p", "udp", "-m", "mark", "--mark", "2", "-j", "REDIRECT", "--to-port", "34011"); if (ret3 != 0 || ret4 != 0) { log_message("failed to add nat redirect rules"); } @@ -2719,10 +2782,10 @@ bool ProxyBridge_Stop(void) running = false; // cleanup iptables - int ret1 = system("iptables -t mangle -D OUTPUT -p tcp -j NFQUEUE --queue-num 0 2>/dev/null"); - int ret2 = system("iptables -t mangle -D OUTPUT -p udp -j NFQUEUE --queue-num 0 2>/dev/null"); - int ret3 = system("iptables -t nat -D OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010 2>/dev/null"); - int ret4 = system("iptables -t nat -D OUTPUT -p udp -m mark --mark 2 -j REDIRECT --to-port 34011 2>/dev/null"); + int ret1 = run_iptables_cmd("-t", "mangle", "-D", "OUTPUT", "-p", "tcp", "-j", "NFQUEUE", "--queue-num", "0", NULL, NULL, NULL, NULL); + int ret2 = run_iptables_cmd("-t", "mangle", "-D", "OUTPUT", "-p", "udp", "-j", "NFQUEUE", "--queue-num", "0", NULL, NULL, NULL, NULL); + int ret3 = run_iptables_cmd("-t", "nat", "-D", "OUTPUT", "-p", "tcp", "-m", "mark", "--mark", "1", "-j", "REDIRECT", "--to-port", "34010"); + int ret4 = run_iptables_cmd("-t", "nat", "-D", "OUTPUT", "-p", "udp", "-m", "mark", "--mark", "2", "-j", "REDIRECT", "--to-port", "34011"); (void)ret1; (void)ret2; (void)ret3; @@ -2981,9 +3044,9 @@ static void library_cleanup(void) { // Even if not running, ensure iptables rules are removed // This handles cases where the app crashed before calling Stop - (void)!system("iptables -t mangle -D OUTPUT -p tcp -j NFQUEUE --queue-num 0 2>/dev/null"); - (void)!system("iptables -t mangle -D OUTPUT -p udp -j NFQUEUE --queue-num 0 2>/dev/null"); - (void)!system("iptables -t nat -D OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010 2>/dev/null"); - (void)!system("iptables -t nat -D OUTPUT -p udp -m mark --mark 2 -j REDIRECT --to-port 34011 2>/dev/null"); + run_iptables_cmd("-t", "mangle", "-D", "OUTPUT", "-p", "tcp", "-j", "NFQUEUE", "--queue-num", "0", NULL, NULL, NULL, NULL); + run_iptables_cmd("-t", "mangle", "-D", "OUTPUT", "-p", "udp", "-j", "NFQUEUE", "--queue-num", "0", NULL, NULL, NULL, NULL); + run_iptables_cmd("-t", "nat", "-D", "OUTPUT", "-p", "tcp", "-m", "mark", "--mark", "1", "-j", "REDIRECT", "--to-port", "34010"); + run_iptables_cmd("-t", "nat", "-D", "OUTPUT", "-p", "udp", "-m", "mark", "--mark", "2", "-j", "REDIRECT", "--to-port", "34011"); } } From 16d47411954f471d5e58da18743db7554a44a6df Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 22:48:22 +0530 Subject: [PATCH 20/58] updated version --- Linux/gui/main.c | 16 +++++++++------- Linux/src/ProxyBridge.c | 1 - Linux/src/ProxyBridge.h | 2 ++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Linux/gui/main.c b/Linux/gui/main.c index eec5c9a..7678b4a 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -1181,18 +1181,19 @@ static void on_about(GtkWidget *widget, gpointer data) { GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_container_set_border_width(GTK_CONTAINER(content_area), 20); - const char *markup = + char *markup = g_strdup_printf( "ProxyBridge\n" - "Version 4.0-Beta\n\n" + "Version %s\n\n" "Universal proxy client for Linux applications\n\n" "Author: Sourav Kalal / InterceptSuite\n\n" "Website: interceptsuite.com\n" "GitHub: InterceptSuite/ProxyBridge\n\n" - "License: MIT"; + "License: MIT", PROXYBRIDGE_VERSION); GtkWidget *label = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label), markup); gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER); + g_free(markup); gtk_box_pack_start(GTK_BOX(content_area), label, TRUE, TRUE, 0); gtk_widget_show_all(dialog); @@ -1296,10 +1297,10 @@ static void on_check_update(GtkWidget *widget, gpointer data) { } // Compare - const char *current_ver = "v4.0.0"; + char *current_tag = g_strdup_printf("v%s", PROXYBRIDGE_VERSION); - if (strcmp(tag_name, current_ver) == 0) { - GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "You are using the latest version (%s).", current_ver); + if (strcmp(tag_name, current_tag) == 0) { + GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "You are using the latest version (%s).", PROXYBRIDGE_VERSION); gtk_dialog_run(GTK_DIALOG(msg)); gtk_widget_destroy(msg); } else { @@ -1308,7 +1309,7 @@ static void on_check_update(GtkWidget *widget, gpointer data) { GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "New version %s is available!\nCurrent version: %s\n\nDo you want to update now?", - tag_name, current_ver); + tag_name, PROXYBRIDGE_VERSION); gtk_dialog_add_button(GTK_DIALOG(dialog), "Download Now", GTK_RESPONSE_ACCEPT); gtk_dialog_add_button(GTK_DIALOG(dialog), "Close", GTK_RESPONSE_CANCEL); @@ -1319,6 +1320,7 @@ static void on_check_update(GtkWidget *widget, gpointer data) { on_create_update_script_and_run(); } } + g_free(current_tag); g_free(tag_name); } diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index 9b4cad0..514e935 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -30,7 +30,6 @@ #define LOCAL_PROXY_PORT 34010 #define LOCAL_UDP_RELAY_PORT 34011 #define MAX_PROCESS_NAME 256 -#define VERSION "4.0-Beta" #define PID_CACHE_SIZE 1024 #define PID_CACHE_TTL_MS 1000 #define NUM_PACKET_THREADS 4 diff --git a/Linux/src/ProxyBridge.h b/Linux/src/ProxyBridge.h index 46e142d..f885c68 100644 --- a/Linux/src/ProxyBridge.h +++ b/Linux/src/ProxyBridge.h @@ -1,6 +1,8 @@ #ifndef PROXYBRIDGE_H #define PROXYBRIDGE_H +#define PROXYBRIDGE_VERSION "4.0.0-Beta" + #include #include From 3f71a34e88b4d105b9623f671bcf6564b6d44af9 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 22:55:55 +0530 Subject: [PATCH 21/58] improved GUI --- Linux/gui/gui.h | 69 ++++++++++++++++++++ Linux/gui/main.c | 160 ++++++++++++++++------------------------------- 2 files changed, 123 insertions(+), 106 deletions(-) create mode 100644 Linux/gui/gui.h diff --git a/Linux/gui/gui.h b/Linux/gui/gui.h new file mode 100644 index 0000000..117128b --- /dev/null +++ b/Linux/gui/gui.h @@ -0,0 +1,69 @@ +#ifndef PROXYBRIDGE_GUI_H +#define PROXYBRIDGE_GUI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ProxyBridge.h" + +// --- Data Structures --- + +typedef struct { + char *process_name; + uint32_t pid; + char *dest_ip; + uint16_t dest_port; + char *proxy_info; + char *timestamp; +} ConnectionData; + +typedef struct { + char *message; +} LogData; + +typedef struct { + GtkWidget *dialog; + GtkWidget *ip_entry; + GtkWidget *port_entry; + GtkWidget *type_combo; + GtkWidget *user_entry; + GtkWidget *pass_entry; + GtkWidget *test_host; + GtkWidget *test_port; + GtkTextBuffer *output_buffer; + GtkWidget *test_btn; +} ConfigInfo; + +typedef struct { + uint32_t id; + char *process_name; + char *target_hosts; + char *target_ports; + RuleProtocol protocol; + RuleAction action; + bool enabled; + bool selected; +} RuleData; + +// --- Thread Communication --- +struct TestRunnerData { + char *host; + uint16_t port; + ConfigInfo *ui_info; +}; + +typedef struct { + char *result_text; + GtkTextBuffer *buffer; + GtkWidget *btn; +} TestResultData; + +#endif // PROXYBRIDGE_GUI_H diff --git a/Linux/gui/main.c b/Linux/gui/main.c index 7678b4a..7b9de75 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -10,15 +10,7 @@ #include #include #include "ProxyBridge.h" - -static long safe_strtol(const char *nptr) { - if (!nptr) return 0; // Handle NULL - char *endptr; - long val = strtol(nptr, &endptr, 10); - // You could check errno here if needed, but for GUI fields 0 is often a safe fallback or filtered before - if (endptr == nptr) return 0; // No digits found - return val; -} +#include "gui.h" // --- Global UI Widgets --- static GtkWidget *window; @@ -29,33 +21,6 @@ static GtkTextBuffer *log_buffer; static GtkWidget *status_bar; static guint status_context_id; -// --- Data Structures for thread-safe UI updates --- -typedef struct { - char *process_name; - uint32_t pid; - char *dest_ip; - uint16_t dest_port; - char *proxy_info; - char *timestamp; -} ConnectionData; - -typedef struct { - char *message; -} LogData; - -typedef struct { - GtkWidget *dialog; - GtkWidget *ip_entry; - GtkWidget *port_entry; - GtkWidget *type_combo; - GtkWidget *user_entry; - GtkWidget *pass_entry; - GtkWidget *test_host; - GtkWidget *test_port; - GtkTextBuffer *output_buffer; - GtkWidget *test_btn; -} ConfigInfo; - // --- Config Globals (Defaults) --- static char g_proxy_ip[256] = ""; static uint16_t g_proxy_port = 0; @@ -63,21 +28,50 @@ static ProxyType g_proxy_type = PROXY_TYPE_SOCKS5; static char g_proxy_user[256] = ""; static char g_proxy_pass[256] = ""; -typedef struct { - uint32_t id; - char *process_name; - char *target_hosts; - char *target_ports; - RuleProtocol protocol; - RuleAction action; - bool enabled; - bool selected; -} RuleData; - static GList *g_rules_list = NULL; static GtkWidget *rules_list_box = NULL; // --- Helper Functions --- + +static long safe_strtol(const char *nptr) { + if (!nptr) return 0; // Handle NULL + char *endptr; + long val = strtol(nptr, &endptr, 10); + // You could check errno here if needed, but for GUI fields 0 is often a safe fallback or filtered before + if (endptr == nptr) return 0; // No digits found + return val; +} + +// --- Helper Functions --- + +// Helper for quick message dialogs +static void show_message(GtkWindow *parent, GtkMessageType type, const char *format, ...) { + va_list args; + va_start(args, format); + char *msg = g_strdup_vprintf(format, args); + va_end(args); + + GtkWidget *dialog = gtk_message_dialog_new(parent, + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + type, + GTK_BUTTONS_OK, + "%s", msg); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_free(msg); +} + +// Helper to limit buffer lines +static void trim_buffer_lines(GtkTextBuffer *buffer, int max_lines) { + if (gtk_text_buffer_get_line_count(buffer) > max_lines) { + GtkTextIter start, next; + gtk_text_buffer_get_start_iter(buffer, &start); + next = start; + gtk_text_iter_forward_line(&next); + gtk_text_buffer_delete(buffer, &start, &next); + } +} + // Forward declaration static void refresh_rules_ui(); @@ -179,13 +173,7 @@ static gboolean update_log_gui(gpointer user_data) { gtk_text_buffer_insert(log_buffer, &end, full_msg, -1); // Limit to 100 lines to prevent memory growth - while (gtk_text_buffer_get_line_count(log_buffer) > 100) { - GtkTextIter start, next; - gtk_text_buffer_get_start_iter(log_buffer, &start); - next = start; - gtk_text_iter_forward_line(&next); - gtk_text_buffer_delete(log_buffer, &start, &next); - } + trim_buffer_lines(log_buffer, 100); free(data->message); free(data); @@ -209,13 +197,7 @@ static gboolean update_connection_gui_append(gpointer user_data) { gtk_text_buffer_insert(conn_buffer, &end, line_buffer, -1); // Limit to 100 lines to prevent memory growth - while (gtk_text_buffer_get_line_count(conn_buffer) > 100) { - GtkTextIter start, next; - gtk_text_buffer_get_start_iter(conn_buffer, &start); - next = start; - gtk_text_iter_forward_line(&next); - gtk_text_buffer_delete(conn_buffer, &start, &next); - } + trim_buffer_lines(conn_buffer, 100); } free_connection_data(data); @@ -244,19 +226,6 @@ static void lib_connection_callback(const char *process_name, uint32_t pid, cons // --- Settings Dialog --- -struct TestRunnerData { - char *host; - uint16_t port; - ConfigInfo *ui_info; -}; - -// Better thread communication -typedef struct { - char *result_text; - GtkTextBuffer *buffer; - GtkWidget *btn; -} TestResultData; - static gboolean on_test_done(gpointer user_data) { TestResultData *data = (TestResultData *)user_data; gtk_text_buffer_set_text(data->buffer, data->result_text, -1); @@ -443,24 +412,18 @@ static void on_proxy_configure(GtkWidget *widget, gpointer data) { const char *port_text = gtk_entry_get_text(GTK_ENTRY(info.port_entry)); if (!ip_text || strlen(ip_text) == 0) { - GtkWidget *err = gtk_message_dialog_new(GTK_WINDOW(info.dialog), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Host (IP/Domain) cannot be empty."); - gtk_dialog_run(GTK_DIALOG(err)); - gtk_widget_destroy(err); + show_message(GTK_WINDOW(info.dialog), GTK_MESSAGE_ERROR, "Host (IP/Domain) cannot be empty."); continue; } if (strspn(port_text, "0123456789") != strlen(port_text) || strlen(port_text) == 0) { - GtkWidget *err = gtk_message_dialog_new(GTK_WINDOW(info.dialog), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Port must be a valid number."); - gtk_dialog_run(GTK_DIALOG(err)); - gtk_widget_destroy(err); + show_message(GTK_WINDOW(info.dialog), GTK_MESSAGE_ERROR, "Port must be a valid number."); continue; } int p = (int)safe_strtol(port_text); if (p < 1 || p > 65535) { - GtkWidget *err = gtk_message_dialog_new(GTK_WINDOW(info.dialog), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Port must be between 1 and 65535."); - gtk_dialog_run(GTK_DIALOG(err)); - gtk_widget_destroy(err); + show_message(GTK_WINDOW(info.dialog), GTK_MESSAGE_ERROR, "Port must be between 1 and 65535."); continue; } @@ -783,9 +746,7 @@ static void on_rule_export_clicked(GtkWidget *widget, gpointer data) { if (((RuleData *)l->data)->selected) { any_selected = true; break; } } if (!any_selected) { - GtkWidget *msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Please select at least one rule to export."); - gtk_dialog_run(GTK_DIALOG(msg)); - gtk_widget_destroy(msg); + show_message(NULL, GTK_MESSAGE_WARNING, "Please select at least one rule to export."); return; } @@ -830,9 +791,7 @@ static void on_rule_export_clicked(GtkWidget *widget, gpointer data) { fprintf(f, "\n]\n"); fclose(f); - GtkWidget *msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "Rules exported successfully."); - gtk_dialog_run(GTK_DIALOG(msg)); - gtk_widget_destroy(msg); + show_message(NULL, GTK_MESSAGE_INFO, "Rules exported successfully."); } g_free(filename); } @@ -909,9 +868,7 @@ static void on_rule_import_clicked(GtkWidget *widget, gpointer data) { if (imported > 0) { refresh_rules_ui(); - GtkWidget *msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "Imported %d rules.", imported); - gtk_dialog_run(GTK_DIALOG(msg)); - gtk_widget_destroy(msg); + show_message(NULL, GTK_MESSAGE_INFO, "Imported %d rules.", imported); } } g_free(filename); @@ -1267,17 +1224,13 @@ static void on_check_update(GtkWidget *widget, gpointer data) { g_free(cmd); if (!result) { - GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Failed to launch update check: %s", error ? error->message : "Unknown error"); - gtk_dialog_run(GTK_DIALOG(msg)); - gtk_widget_destroy(msg); + show_message(GTK_WINDOW(window), GTK_MESSAGE_ERROR, "Failed to launch update check: %s", error ? error->message : "Unknown error"); if (error) g_error_free(error); return; } if (exit_status != 0 || !standard_output || strlen(standard_output) == 0) { - GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Update check failed (Exit: %d).", exit_status); - gtk_dialog_run(GTK_DIALOG(msg)); - gtk_widget_destroy(msg); + show_message(GTK_WINDOW(window), GTK_MESSAGE_ERROR, "Update check failed (Exit: %d).", exit_status); g_free(standard_output); g_free(standard_error); return; @@ -1290,9 +1243,7 @@ static void on_check_update(GtkWidget *widget, gpointer data) { g_free(standard_error); if (!tag_name) { - GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Could not parse version info.\nResponse might be rate limited."); - gtk_dialog_run(GTK_DIALOG(msg)); - gtk_widget_destroy(msg); + show_message(GTK_WINDOW(window), GTK_MESSAGE_WARNING, "Could not parse version info.\nResponse might be rate limited."); return; } @@ -1300,9 +1251,7 @@ static void on_check_update(GtkWidget *widget, gpointer data) { char *current_tag = g_strdup_printf("v%s", PROXYBRIDGE_VERSION); if (strcmp(tag_name, current_tag) == 0) { - GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "You are using the latest version (%s).", PROXYBRIDGE_VERSION); - gtk_dialog_run(GTK_DIALOG(msg)); - gtk_widget_destroy(msg); + show_message(GTK_WINDOW(window), GTK_MESSAGE_INFO, "You are using the latest version (%s).", PROXYBRIDGE_VERSION); } else { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, @@ -1349,8 +1298,7 @@ int main(int argc, char *argv[]) { // Can't show GUI dialog easily without GTK init which might fail if no display. // Try init, then dialog. gtk_init(&argc, &argv); - GtkWidget *dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "ProxyBridge must be run as root (sudo)."); - gtk_dialog_run(GTK_DIALOG(dialog)); + show_message(NULL, GTK_MESSAGE_ERROR, "ProxyBridge must be run as root (sudo)."); return 1; } From 05a8217d0dea164b421d048be7c60fddd2c38b87 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 23:07:43 +0530 Subject: [PATCH 22/58] GUi Code Split --- Linux/build.sh | 13 +- Linux/gui/gui.h | 40 ++ Linux/gui/gui_logs.c | 126 ++++ Linux/gui/gui_rules.c | 463 +++++++++++++ Linux/gui/gui_settings.c | 219 +++++++ Linux/gui/gui_utils.c | 91 +++ Linux/gui/main.c | 1336 ++------------------------------------ 7 files changed, 1008 insertions(+), 1280 deletions(-) create mode 100644 Linux/gui/gui_logs.c create mode 100644 Linux/gui/gui_rules.c create mode 100644 Linux/gui/gui_settings.c create mode 100644 Linux/gui/gui_utils.c diff --git a/Linux/build.sh b/Linux/build.sh index 7d8c800..6d5f49b 100755 --- a/Linux/build.sh +++ b/Linux/build.sh @@ -51,10 +51,17 @@ if pkg-config --exists gtk+-3.0; then GUI_CFLAGS="-Wall -Wno-unused-parameter -O3 -Isrc -D_GNU_SOURCE -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security -Werror=format-security -fno-strict-overflow -fno-delete-null-pointer-checks -fwrapv $(pkg-config --cflags gtk+-3.0)" GUI_LDFLAGS="-Lsrc -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s -Wl,-rpath,'$ORIGIN/.' -lproxybridge -lpthread $(pkg-config --libs gtk+-3.0) -export-dynamic" - gcc $GUI_CFLAGS -c gui/main.c -o gui_main.o - gcc -o ProxyBridgeGUI gui_main.o $GUI_LDFLAGS + # Compile all GUI source files + GUI_OBJS="" + for src in gui/*.c; do + obj="${src%.c}.o" + gcc $GUI_CFLAGS -c "$src" -o "$obj" + GUI_OBJS="$GUI_OBJS $obj" + done + + gcc -o ProxyBridgeGUI $GUI_OBJS $GUI_LDFLAGS - rm -f gui_main.o + rm -f gui/*.o echo "GUI build successful" else echo "GTK3 not found. Skipping GUI build." diff --git a/Linux/gui/gui.h b/Linux/gui/gui.h index 117128b..d0e57de 100644 --- a/Linux/gui/gui.h +++ b/Linux/gui/gui.h @@ -66,4 +66,44 @@ typedef struct { GtkWidget *btn; } TestResultData; +// --- Globals (External) --- +extern GtkWidget *window; +extern GtkTextBuffer *conn_buffer; +extern GtkTextBuffer *log_buffer; +extern GtkWidget *status_bar; +extern guint status_context_id; + +extern char g_proxy_ip[256]; +extern uint16_t g_proxy_port; +extern ProxyType g_proxy_type; +extern char g_proxy_user[256]; +extern char g_proxy_pass[256]; + +extern GList *g_rules_list; + +// --- Function Prototypes --- + +// Utils +long safe_strtol(const char *nptr); +void show_message(GtkWindow *parent, GtkMessageType type, const char *format, ...); +void trim_buffer_lines(GtkTextBuffer *buffer, int max_lines); +char* get_current_time_str(); +char *escape_json_string(const char *src); +char *extract_sub_json_str(const char *json, const char *key); +bool extract_sub_json_bool(const char *json, const char *key); + +// Settings +void on_proxy_configure(GtkWidget *widget, gpointer data); + +// Rules +void on_proxy_rules_clicked(GtkWidget *widget, gpointer data); + +// Logs +void lib_log_callback(const char *message); +void lib_connection_callback(const char *process_name, uint32_t pid, const char *dest_ip, uint16_t dest_port, const char *proxy_info); +void on_search_conn_changed(GtkSearchEntry *entry, gpointer user_data); +void on_search_log_changed(GtkSearchEntry *entry, gpointer user_data); +void on_clear_conn_clicked(GtkButton *button, gpointer user_data); +void on_clear_log_clicked(GtkButton *button, gpointer user_data); + #endif // PROXYBRIDGE_GUI_H diff --git a/Linux/gui/gui_logs.c b/Linux/gui/gui_logs.c new file mode 100644 index 0000000..8803348 --- /dev/null +++ b/Linux/gui/gui_logs.c @@ -0,0 +1,126 @@ +#include "gui.h" + +// Filter Function for TextView +static void filter_text_view(GtkTextBuffer *buffer, const char *text) { + if (!buffer) return; + + GtkTextIter start, end; + gtk_text_buffer_get_bounds(buffer, &start, &end); + gtk_text_buffer_remove_tag_by_name(buffer, "hidden", &start, &end); + + if (!text || strlen(text) == 0) return; + + GtkTextIter line_start = start; + while (!gtk_text_iter_is_end(&line_start)) { + GtkTextIter line_end = line_start; + if (!gtk_text_iter_ends_line(&line_end)) + gtk_text_iter_forward_to_line_end(&line_end); + + char *line_text = gtk_text_buffer_get_text(buffer, &line_start, &line_end, FALSE); + + // Case-insensitive search + char *lower_line = g_utf8_strdown(line_text, -1); + char *lower_search = g_utf8_strdown(text, -1); + + if (!strstr(lower_line, lower_search)) { + GtkTextIter next_line = line_end; + gtk_text_iter_forward_char(&next_line); // include newline + gtk_text_buffer_apply_tag_by_name(buffer, "hidden", &line_start, &next_line); + } + + g_free(lower_line); + g_free(lower_search); + g_free(line_text); + + gtk_text_iter_forward_line(&line_start); + } +} + +void on_search_conn_changed(GtkSearchEntry *entry, gpointer user_data) { + const char *text = gtk_entry_get_text(GTK_ENTRY(entry)); + filter_text_view(conn_buffer, text); +} + +void on_search_log_changed(GtkSearchEntry *entry, gpointer user_data) { + const char *text = gtk_entry_get_text(GTK_ENTRY(entry)); + filter_text_view(log_buffer, text); +} + +void on_clear_conn_clicked(GtkButton *button, gpointer user_data) { + if (conn_buffer) gtk_text_buffer_set_text(conn_buffer, "", 0); +} + +void on_clear_log_clicked(GtkButton *button, gpointer user_data) { + if (log_buffer) gtk_text_buffer_set_text(log_buffer, "", 0); +} + +static void free_connection_data(ConnectionData *data) { + if (data) { + free(data->process_name); + free(data->dest_ip); + free(data->proxy_info); + free(data->timestamp); + free(data); + } +} + +// Thread-safe idle callback for Log +static gboolean update_log_gui(gpointer user_data) { + LogData *data = (LogData *)user_data; + if (!data) return FALSE; + + GtkTextIter end; + gtk_text_buffer_get_end_iter(log_buffer, &end); + + char *time_str = get_current_time_str(); + char full_msg[1200]; + snprintf(full_msg, sizeof(full_msg), "%s %s\n", time_str, data->message); + free(time_str); + + gtk_text_buffer_insert(log_buffer, &end, full_msg, -1); + + trim_buffer_lines(log_buffer, 100); + + free(data->message); + free(data); + return FALSE; // Remove source +} + +// Thread-safe idle callback for Connection +static gboolean update_connection_gui_append(gpointer user_data) { + ConnectionData *data = (ConnectionData *)user_data; + if (!data) return FALSE; + + if (conn_buffer) { + GtkTextIter end; + gtk_text_buffer_get_end_iter(conn_buffer, &end); + + char line_buffer[1024]; + snprintf(line_buffer, sizeof(line_buffer), "%s %s (PID:%u) -> %s:%u via %s\n", + data->timestamp, data->process_name, data->pid, data->dest_ip, data->dest_port, data->proxy_info); + + gtk_text_buffer_insert(conn_buffer, &end, line_buffer, -1); + + trim_buffer_lines(conn_buffer, 100); + } + + free_connection_data(data); + return FALSE; +} + +void lib_log_callback(const char *message) { + LogData *data = malloc(sizeof(LogData)); + data->message = strdup(message); + g_idle_add(update_log_gui, data); +} + +void lib_connection_callback(const char *process_name, uint32_t pid, const char *dest_ip, uint16_t dest_port, const char *proxy_info) { + ConnectionData *data = malloc(sizeof(ConnectionData)); + data->process_name = strdup(process_name); + data->pid = pid; + data->dest_ip = strdup(dest_ip); + data->dest_port = dest_port; + data->proxy_info = strdup(proxy_info); + data->timestamp = get_current_time_str(); + g_idle_add(update_connection_gui_append, data); +} diff --git a/Linux/gui/gui_rules.c b/Linux/gui/gui_rules.c new file mode 100644 index 0000000..52b1718 --- /dev/null +++ b/Linux/gui/gui_rules.c @@ -0,0 +1,463 @@ +#include "gui.h" + +static GtkWidget *rules_list_box = NULL; +static GtkWidget *btn_select_all_header = NULL; + +// Forward declaration +static void refresh_rules_ui(); + +static void free_rule_data(RuleData *rule) { + if (rule) { + if (rule->process_name) free(rule->process_name); + if (rule->target_hosts) free(rule->target_hosts); + if (rule->target_ports) free(rule->target_ports); + free(rule); + } +} + +static void on_rule_delete(GtkWidget *widget, gpointer data) { + RuleData *rule = (RuleData *)data; + ProxyBridge_DeleteRule(rule->id); + g_rules_list = g_list_remove(g_rules_list, rule); + free_rule_data(rule); + refresh_rules_ui(); +} + +static void on_rule_toggle(GtkToggleButton *btn, gpointer data) { + RuleData *rule = (RuleData *)data; + rule->enabled = gtk_toggle_button_get_active(btn); + if (rule->enabled) ProxyBridge_EnableRule(rule->id); + else ProxyBridge_DisableRule(rule->id); +} + +static void on_save_rule(GtkWidget *widget, gpointer data) { + GtkWidget **widgets = (GtkWidget **)data; + GtkWidget *dialog = widgets[0]; + RuleData *edit_rule = (RuleData *)widgets[6]; // If not null, we are editing + + const char *proc = gtk_entry_get_text(GTK_ENTRY(widgets[1])); + const char *hosts = gtk_entry_get_text(GTK_ENTRY(widgets[2])); + const char *ports = gtk_entry_get_text(GTK_ENTRY(widgets[3])); + RuleProtocol proto = gtk_combo_box_get_active(GTK_COMBO_BOX(widgets[4])); + RuleAction action = gtk_combo_box_get_active(GTK_COMBO_BOX(widgets[5])); + + if (strlen(proc) == 0) return; // Error + + if (edit_rule) { + ProxyBridge_EditRule(edit_rule->id, proc, hosts, ports, proto, action); + free(edit_rule->process_name); edit_rule->process_name = strdup(proc); + free(edit_rule->target_hosts); edit_rule->target_hosts = strdup(hosts); + free(edit_rule->target_ports); edit_rule->target_ports = strdup(ports); + edit_rule->protocol = proto; + edit_rule->action = action; + } else { + uint32_t new_id = ProxyBridge_AddRule(proc, hosts, ports, proto, action); + RuleData *new_rule = malloc(sizeof(RuleData)); + new_rule->id = new_id; + new_rule->process_name = strdup(proc); + new_rule->target_hosts = strdup(hosts); + new_rule->target_ports = strdup(ports); + new_rule->protocol = proto; + new_rule->action = action; + new_rule->enabled = true; + new_rule->selected = false; + g_rules_list = g_list_append(g_rules_list, new_rule); + } + refresh_rules_ui(); + gtk_widget_destroy(dialog); + free(widgets); +} + +static void on_browse_clicked(GtkWidget *widget, gpointer data) { + GtkWidget *entry = (GtkWidget *)data; + GtkWidget *dialog = gtk_file_chooser_dialog_new("Select Application", + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Select", GTK_RESPONSE_ACCEPT, + NULL); + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + char *base = g_path_get_basename(filename); + gtk_entry_set_text(GTK_ENTRY(entry), base); + g_free(base); + g_free(filename); + } + gtk_widget_destroy(dialog); +} + +static void open_rule_dialog(RuleData *rule) { + GtkWidget *dialog = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(dialog), rule ? "Edit Rule" : "Add Rule"); + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window)); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 400); + + GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + GtkWidget *grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(grid), 8); + gtk_grid_set_column_spacing(GTK_GRID(grid), 10); + gtk_container_set_border_width(GTK_CONTAINER(grid), 15); + + // Process + GtkWidget *proc_entry = gtk_entry_new(); + GtkWidget *browse_btn = gtk_button_new_with_label("Browse..."); + g_signal_connect(browse_btn, "clicked", G_CALLBACK(on_browse_clicked), proc_entry); + GtkWidget *proc_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_pack_start(GTK_BOX(proc_box), proc_entry, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(proc_box), browse_btn, FALSE, FALSE, 0); + + GtkWidget *host_entry = gtk_entry_new(); + GtkWidget *port_entry = gtk_entry_new(); + + // Protocol + GtkWidget *proto_combo = gtk_combo_box_text_new(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "TCP"); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "UDP"); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "BOTH"); + + // Action + GtkWidget *action_combo = gtk_combo_box_text_new(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "PROXY"); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "DIRECT"); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "BLOCK"); + + if (rule) { + gtk_entry_set_text(GTK_ENTRY(proc_entry), rule->process_name); + gtk_entry_set_text(GTK_ENTRY(host_entry), rule->target_hosts); + gtk_entry_set_text(GTK_ENTRY(port_entry), rule->target_ports); + gtk_combo_box_set_active(GTK_COMBO_BOX(proto_combo), rule->protocol); + gtk_combo_box_set_active(GTK_COMBO_BOX(action_combo), rule->action); + } else { + gtk_entry_set_text(GTK_ENTRY(port_entry), "*"); + gtk_entry_set_text(GTK_ENTRY(host_entry), "*"); + gtk_combo_box_set_active(GTK_COMBO_BOX(proto_combo), 2); // BOTH + gtk_combo_box_set_active(GTK_COMBO_BOX(action_combo), 0); // PROXY + } + + int row = 0; + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Process Name:"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), proc_box, 1, row, 1, 1); row++; + + GtkWidget *proc_hint = gtk_label_new("Example: firefox; chrome; /usr/bin/wget"); + gtk_style_context_add_class(gtk_widget_get_style_context(proc_hint), "dim-label"); + gtk_widget_set_halign(proc_hint, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(grid), proc_hint, 1, row, 1, 1); row++; + + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target Host:"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), host_entry, 1, row, 1, 1); row++; + + GtkWidget *host_hint = gtk_label_new("Example: 192.168.1.*; 10.0.0.1-50; *"); + gtk_style_context_add_class(gtk_widget_get_style_context(host_hint), "dim-label"); + gtk_widget_set_halign(host_hint, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(grid), host_hint, 1, row, 1, 1); row++; + + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target Port:"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), port_entry, 1, row, 1, 1); row++; + + GtkWidget *port_hint = gtk_label_new("Example: 80; 443; 8000-8080; *"); + gtk_style_context_add_class(gtk_widget_get_style_context(port_hint), "dim-label"); + gtk_widget_set_halign(port_hint, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(grid), port_hint, 1, row, 1, 1); row++; + + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Protocol:"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), proto_combo, 1, row, 1, 1); row++; + + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Action:"), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), action_combo, 1, row, 1, 1); row++; + + gtk_container_add(GTK_CONTAINER(content), grid); + + GtkWidget *save_btn = gtk_button_new_with_label("Save"); + GtkWidget *cancel_btn = gtk_button_new_with_label("Cancel"); + gtk_dialog_add_action_widget(GTK_DIALOG(dialog), cancel_btn, GTK_RESPONSE_CANCEL); + gtk_dialog_add_action_widget(GTK_DIALOG(dialog), save_btn, GTK_RESPONSE_ACCEPT); + + GtkWidget **data = malloc(7 * sizeof(GtkWidget*)); + data[0] = dialog; + data[1] = proc_entry; + data[2] = host_entry; + data[3] = port_entry; + data[4] = proto_combo; + data[5] = action_combo; + data[6] = (GtkWidget*)rule; + + g_signal_connect(save_btn, "clicked", G_CALLBACK(on_save_rule), data); + g_signal_connect(cancel_btn, "clicked", G_CALLBACK(gtk_widget_destroy), NULL); + gtk_widget_show_all(dialog); +} + +static void on_rule_edit(GtkWidget *widget, gpointer data) { + on_proxy_rules_clicked(NULL, NULL); // Re-open if closed? Usually modal. + // Wait, on_rule_edit is clicked FROM the rules list. + open_rule_dialog((RuleData *)data); +} + +static void on_rule_add_clicked(GtkWidget *widget, gpointer data) { + open_rule_dialog(NULL); +} + +static void on_rule_select_toggle(GtkToggleButton *btn, gpointer data) { + RuleData *rule = (RuleData *)data; + rule->selected = gtk_toggle_button_get_active(btn); + if (btn_select_all_header) { + bool all_selected = (g_rules_list != NULL); + if (g_rules_list == NULL) all_selected = false; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *r = (RuleData *)l->data; + if (!r->selected) { + all_selected = false; + break; + } + } + gtk_button_set_label(GTK_BUTTON(btn_select_all_header), all_selected ? "Deselect All" : "Select All"); + } +} + +static void on_rule_export_clicked(GtkWidget *widget, gpointer data) { + if (!g_rules_list) return; + bool any_selected = false; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + if (((RuleData *)l->data)->selected) { any_selected = true; break; } + } + if (!any_selected) { + show_message(NULL, GTK_MESSAGE_WARNING, "Please select at least one rule to export."); + return; + } + GtkWidget *dialog = gtk_file_chooser_dialog_new("Export Rules", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_SAVE, "_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "proxy_rules.json"); + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + FILE *f = fopen(filename, "w"); + if (f) { + fprintf(f, "[\n"); + bool first = true; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *r = (RuleData *)l->data; + if (!r->selected) continue; + if (!first) fprintf(f, ",\n"); + char *proc = escape_json_string(r->process_name); + char *host = escape_json_string(r->target_hosts); + char *port = escape_json_string(r->target_ports); + const char *proto = (r->protocol == RULE_PROTOCOL_TCP) ? "TCP" : (r->protocol == RULE_PROTOCOL_UDP ? "UDP" : "BOTH"); + const char *act = (r->action == RULE_ACTION_PROXY) ? "PROXY" : (r->action == RULE_ACTION_DIRECT ? "DIRECT" : "BLOCK"); + fprintf(f, " {\n \"processNames\": \"%s\",\n \"targetHosts\": \"%s\",\n \"targetPorts\": \"%s\",\n \"protocol\": \"%s\",\n \"action\": \"%s\",\n \"enabled\": %s\n }", proc, host, port, proto, act, r->enabled ? "true" : "false"); + g_free(proc); g_free(host); g_free(port); + first = false; + } + fprintf(f, "\n]\n"); + fclose(f); + show_message(NULL, GTK_MESSAGE_INFO, "Rules exported successfully."); + } + g_free(filename); + } + gtk_widget_destroy(dialog); +} + +static void on_rule_import_clicked(GtkWidget *widget, gpointer data) { + GtkWidget *dialog = gtk_file_chooser_dialog_new("Import Rules", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL); + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + char *content = NULL; + gsize len; + if (g_file_get_contents(filename, &content, &len, NULL)) { + char *curr = content; + int imported = 0; + while ((curr = strchr(curr, '{')) != NULL) { + char *end = strchr(curr, '}'); + if (!end) break; + char saved = *end; *end = '\0'; + + char *proc = extract_sub_json_str(curr, "processNames"); + char *host = extract_sub_json_str(curr, "targetHosts"); + char *port = extract_sub_json_str(curr, "targetPorts"); + char *proto_s = extract_sub_json_str(curr, "protocol"); + char *act_s = extract_sub_json_str(curr, "action"); + bool en = extract_sub_json_bool(curr, "enabled"); + + if (proc && host && port && proto_s && act_s) { + RuleProtocol p = RULE_PROTOCOL_BOTH; + if (strcmp(proto_s, "TCP") == 0) p = RULE_PROTOCOL_TCP; + else if (strcmp(proto_s, "UDP") == 0) p = RULE_PROTOCOL_UDP; + RuleAction a = RULE_ACTION_PROXY; + if (strcmp(act_s, "DIRECT") == 0) a = RULE_ACTION_DIRECT; + else if (strcmp(act_s, "BLOCK") == 0) a = RULE_ACTION_BLOCK; + + uint32_t nid = ProxyBridge_AddRule(proc, host, port, p, a); + if (!en) ProxyBridge_DisableRule(nid); + RuleData *nd = malloc(sizeof(RuleData)); + nd->id = nid; nd->process_name = strdup(proc); nd->target_hosts = strdup(host); + nd->target_ports = strdup(port); nd->protocol = p; nd->action = a; nd->enabled = en; nd->selected = false; + g_rules_list = g_list_append(g_rules_list, nd); + imported++; + } + g_free(proc); g_free(host); g_free(port); g_free(proto_s); g_free(act_s); + *end = saved; curr = end + 1; + } + g_free(content); + if (imported > 0) { refresh_rules_ui(); show_message(NULL, GTK_MESSAGE_INFO, "Imported %d rules.", imported); } + } + g_free(filename); + } + gtk_widget_destroy(dialog); +} + +static void on_bulk_delete_clicked(GtkWidget *widget, gpointer data) { + if (!g_rules_list) return; + GList *iter = g_rules_list; + GList *to_delete = NULL; + while (iter != NULL) { + RuleData *rule = (RuleData *)iter->data; + if (rule->selected) to_delete = g_list_append(to_delete, rule); + iter = iter->next; + } + if (!to_delete) return; + for (GList *d = to_delete; d != NULL; d = d->next) { + RuleData *rule = (RuleData *)d->data; + ProxyBridge_DeleteRule(rule->id); + g_rules_list = g_list_remove(g_rules_list, rule); + free_rule_data(rule); + } + g_list_free(to_delete); + refresh_rules_ui(); +} + +static void on_select_all_clicked(GtkWidget *widget, gpointer data) { + if (!g_rules_list) return; + bool all_selected = true; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + if (!((RuleData *)l->data)->selected) { all_selected = false; break; } + } + bool new_state = !all_selected; + for (GList *l = g_rules_list; l != NULL; l = l->next) ((RuleData *)l->data)->selected = new_state; + refresh_rules_ui(); +} + +static void refresh_rules_ui() { + if (!rules_list_box) return; + GList *children = gtk_container_get_children(GTK_CONTAINER(rules_list_box)); + for(GList *iter = children; iter != NULL; iter = g_list_next(iter)) gtk_widget_destroy(GTK_WIDGET(iter->data)); + g_list_free(children); + + if (btn_select_all_header) { + bool all_selected = (g_rules_list != NULL); + if (g_rules_list == NULL) all_selected = false; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + if (!((RuleData *)l->data)->selected) { all_selected = false; break; } + } + gtk_button_set_label(GTK_BUTTON(btn_select_all_header), all_selected ? "Deselect All" : "Select All"); + } + + GtkWidget *grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(grid), 10); + gtk_grid_set_column_spacing(GTK_GRID(grid), 15); + gtk_container_add(GTK_CONTAINER(rules_list_box), grid); + + int row = 0; + gtk_grid_attach(GTK_GRID(grid), gtk_label_new(" "), 0, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Enable"), 1, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Actions"), 2, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("SR"), 3, row, 1, 1); + GtkWidget *h_proc = gtk_label_new("Process"); gtk_widget_set_halign(h_proc, GTK_ALIGN_START); + gtk_widget_set_hexpand(h_proc, TRUE); gtk_grid_attach(GTK_GRID(grid), h_proc, 4, row, 1, 1); + GtkWidget *h_host = gtk_label_new("Target Hosts"); gtk_widget_set_halign(h_host, GTK_ALIGN_START); + gtk_widget_set_hexpand(h_host, TRUE); gtk_grid_attach(GTK_GRID(grid), h_host, 5, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Protocol"), 6, row, 1, 1); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Action"), 7, row, 1, 1); + row++; + gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 8, 1); row++; + + int sr_counter = 1; + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *r = (RuleData *)l->data; + GtkWidget *chk_sel = gtk_check_button_new(); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk_sel), r->selected); + g_signal_connect(chk_sel, "toggled", G_CALLBACK(on_rule_select_toggle), r); + gtk_grid_attach(GTK_GRID(grid), chk_sel, 0, row, 1, 1); + + GtkWidget *chk = gtk_check_button_new(); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk), r->enabled); + g_signal_connect(chk, "toggled", G_CALLBACK(on_rule_toggle), r); + gtk_grid_attach(GTK_GRID(grid), chk, 1, row, 1, 1); + + GtkWidget *act_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2); + GtkWidget *btn_edit = gtk_button_new_with_label("Edit"); + g_signal_connect(btn_edit, "clicked", G_CALLBACK(on_rule_edit), r); + GtkWidget *btn_del = gtk_button_new_with_label("Delete"); + g_signal_connect(btn_del, "clicked", G_CALLBACK(on_rule_delete), r); + gtk_box_pack_start(GTK_BOX(act_box), btn_edit, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(act_box), btn_del, FALSE, FALSE, 0); + gtk_grid_attach(GTK_GRID(grid), act_box, 2, row, 1, 1); + + char sr_str[16]; snprintf(sr_str, sizeof(sr_str), "%d", sr_counter++); + gtk_grid_attach(GTK_GRID(grid), gtk_label_new(sr_str), 3, row, 1, 1); + + GtkWidget *l_proc = gtk_label_new(r->process_name); gtk_widget_set_halign(l_proc, GTK_ALIGN_START); + gtk_label_set_ellipsize(GTK_LABEL(l_proc), PANGO_ELLIPSIZE_END); + gtk_grid_attach(GTK_GRID(grid), l_proc, 4, row, 1, 1); + + GtkWidget *l_host = gtk_label_new(r->target_hosts); gtk_widget_set_halign(l_host, GTK_ALIGN_START); + gtk_label_set_ellipsize(GTK_LABEL(l_host), PANGO_ELLIPSIZE_END); + gtk_grid_attach(GTK_GRID(grid), l_host, 5, row, 1, 1); + + const char* proto_strs[] = {"TCP", "UDP", "BOTH"}; + gtk_grid_attach(GTK_GRID(grid), gtk_label_new(proto_strs[r->protocol]), 6, row, 1, 1); + + const char* action_strs[] = {"PROXY", "DIRECT", "BLOCK"}; + GtkWidget *l_act = gtk_label_new(action_strs[r->action]); + GtkStyleContext *context = gtk_widget_get_style_context(l_act); + if (r->action == RULE_ACTION_PROXY) gtk_style_context_add_class(context, "success"); + else if (r->action == RULE_ACTION_DIRECT) gtk_style_context_add_class(context, "info"); + else gtk_style_context_add_class(context, "warning"); + gtk_grid_attach(GTK_GRID(grid), l_act, 7, row, 1, 1); + + row++; + gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 8, 1); row++; + } + gtk_widget_show_all(rules_list_box); +} + +void on_proxy_rules_clicked(GtkWidget *widget, gpointer data) { + GtkWidget *dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(dialog), "Process Rules"); + gtk_window_set_default_size(GTK_WINDOW(dialog), 800, 500); + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window)); + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 20); + gtk_container_add(GTK_CONTAINER(dialog), vbox); + + GtkWidget *header_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + GtkWidget *title = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(title), "Process Rules"); + GtkWidget *add_btn = gtk_button_new_with_label("+ Add Rule"); + g_signal_connect(add_btn, "clicked", G_CALLBACK(on_rule_add_clicked), NULL); + btn_select_all_header = gtk_button_new_with_label("Select All"); + g_signal_connect(btn_select_all_header, "clicked", G_CALLBACK(on_select_all_clicked), NULL); + GtkWidget *del_all_btn = gtk_button_new_with_label("Delete Selected"); + g_signal_connect(del_all_btn, "clicked", G_CALLBACK(on_bulk_delete_clicked), NULL); + GtkWidget *import_btn = gtk_button_new_with_label("Import"); + g_signal_connect(import_btn, "clicked", G_CALLBACK(on_rule_import_clicked), NULL); + GtkWidget *export_btn = gtk_button_new_with_label("Export"); + g_signal_connect(export_btn, "clicked", G_CALLBACK(on_rule_export_clicked), NULL); + + gtk_box_pack_start(GTK_BOX(header_box), title, FALSE, FALSE, 0); + GtkWidget *spacer = gtk_label_new(""); + gtk_widget_set_hexpand(spacer, TRUE); + gtk_box_pack_start(GTK_BOX(header_box), spacer, TRUE, TRUE, 0); + gtk_box_pack_end(GTK_BOX(header_box), add_btn, FALSE, FALSE, 5); + gtk_box_pack_end(GTK_BOX(header_box), btn_select_all_header, FALSE, FALSE, 5); + gtk_box_pack_end(GTK_BOX(header_box), del_all_btn, FALSE, FALSE, 5); + gtk_box_pack_end(GTK_BOX(header_box), export_btn, FALSE, FALSE, 5); + gtk_box_pack_end(GTK_BOX(header_box), import_btn, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(vbox), header_box, FALSE, FALSE, 0); + + GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_vexpand(scrolled, TRUE); + rules_list_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + gtk_container_add(GTK_CONTAINER(scrolled), rules_list_box); + gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0); + + refresh_rules_ui(); + gtk_widget_show_all(dialog); +} diff --git a/Linux/gui/gui_settings.c b/Linux/gui/gui_settings.c new file mode 100644 index 0000000..9a13d48 --- /dev/null +++ b/Linux/gui/gui_settings.c @@ -0,0 +1,219 @@ +#include "gui.h" + +// --- Settings Dialog --- + +static gboolean on_test_done(gpointer user_data) { + TestResultData *data = (TestResultData *)user_data; + gtk_text_buffer_set_text(data->buffer, data->result_text, -1); + gtk_widget_set_sensitive(data->btn, TRUE); + + free(data->result_text); + free(data); + return FALSE; +} + +static gpointer run_test_thread(gpointer user_data) { + struct TestRunnerData *req = (struct TestRunnerData *)user_data; + + char *buffer = malloc(4096); + memset(buffer, 0, 4096); + + // Check callback if needed, but TestConnection usually returns result in buffer + ProxyBridge_TestConnection(req->host, req->port, buffer, 4096); + + TestResultData *res = malloc(sizeof(TestResultData)); + res->result_text = buffer; + res->buffer = req->ui_info->output_buffer; + res->btn = req->ui_info->test_btn; + + g_idle_add(on_test_done, res); + + free(req->host); + free(req); + return NULL; +} + +static void on_start_test_clicked(GtkWidget *widget, gpointer data) { + ConfigInfo *info = (ConfigInfo *)data; + + // 1. Validate Proxy Config + const char *ip_text = gtk_entry_get_text(GTK_ENTRY(info->ip_entry)); + const char *port_text = gtk_entry_get_text(GTK_ENTRY(info->port_entry)); + + if (!ip_text || strlen(ip_text) == 0 || strspn(port_text, "0123456789") != strlen(port_text) || strlen(port_text) == 0) { + gtk_text_buffer_set_text(info->output_buffer, "Error: Invalid Proxy IP or Port.", -1); + return; + } + + // 2. Set Proxy Config + ProxyType type = (gtk_combo_box_get_active(GTK_COMBO_BOX(info->type_combo)) == 0) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; + int port = (int)safe_strtol(port_text); + const char *user = gtk_entry_get_text(GTK_ENTRY(info->user_entry)); + const char *pass = gtk_entry_get_text(GTK_ENTRY(info->pass_entry)); + + ProxyBridge_SetProxyConfig(type, ip_text, port, user, pass); + + // 3. Get Test Target + const char *t_host = gtk_entry_get_text(GTK_ENTRY(info->test_host)); + const char *t_port_s = gtk_entry_get_text(GTK_ENTRY(info->test_port)); + + if (!t_host || strlen(t_host) == 0) t_host = "google.com"; + int t_port = (int)safe_strtol(t_port_s); + if (t_port <= 0) t_port = 80; + + // 4. Update UI + gtk_text_buffer_set_text(info->output_buffer, "Testing connection... Please wait...", -1); + gtk_widget_set_sensitive(info->test_btn, FALSE); + + // 5. Run Thread + struct TestRunnerData *req = malloc(sizeof(struct TestRunnerData)); + req->host = strdup(t_host); + req->port = t_port; + req->ui_info = info; + + GThread *thread = g_thread_new("test_conn", run_test_thread, req); + g_thread_unref(thread); +} + +void on_proxy_configure(GtkWidget *widget, gpointer data) { + ConfigInfo info; + + GtkWidget *content_area; + GtkWidget *grid; + + info.dialog = gtk_dialog_new_with_buttons("Proxy Settings", + GTK_WINDOW(window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + "Cancel", GTK_RESPONSE_CANCEL, + "Save", GTK_RESPONSE_ACCEPT, + NULL); + // Increase width for log + gtk_window_set_default_size(GTK_WINDOW(info.dialog), 600, 500); + + content_area = gtk_dialog_get_content_area(GTK_DIALOG(info.dialog)); + grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(grid), 5); + gtk_grid_set_column_spacing(GTK_GRID(grid), 10); + gtk_container_set_border_width(GTK_CONTAINER(grid), 10); + gtk_box_pack_start(GTK_BOX(content_area), grid, TRUE, TRUE, 0); + + // Type + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Type:"), 0, 0, 1, 1); + info.type_combo = gtk_combo_box_text_new(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(info.type_combo), "HTTP"); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(info.type_combo), "SOCKS5"); + gtk_combo_box_set_active(GTK_COMBO_BOX(info.type_combo), g_proxy_type == PROXY_TYPE_HTTP ? 0 : 1); + gtk_grid_attach(GTK_GRID(grid), info.type_combo, 1, 0, 3, 1); // Span 3 + + // IP + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Host:"), 0, 1, 1, 1); + info.ip_entry = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(info.ip_entry), g_proxy_ip); + gtk_widget_set_hexpand(info.ip_entry, TRUE); + gtk_grid_attach(GTK_GRID(grid), info.ip_entry, 1, 1, 3, 1); + + // Port + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Port:"), 0, 2, 1, 1); + info.port_entry = gtk_entry_new(); + if (g_proxy_port != 0) { + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%d", g_proxy_port); + gtk_entry_set_text(GTK_ENTRY(info.port_entry), port_str); + } + gtk_grid_attach(GTK_GRID(grid), info.port_entry, 1, 2, 3, 1); + + // User + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Username:"), 0, 3, 1, 1); + info.user_entry = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(info.user_entry), g_proxy_user); + gtk_grid_attach(GTK_GRID(grid), info.user_entry, 1, 3, 3, 1); + + // Pass + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Password:"), 0, 4, 1, 1); + info.pass_entry = gtk_entry_new(); + gtk_entry_set_visibility(GTK_ENTRY(info.pass_entry), FALSE); + gtk_entry_set_text(GTK_ENTRY(info.pass_entry), g_proxy_pass); + gtk_grid_attach(GTK_GRID(grid), info.pass_entry, 1, 4, 3, 1); + + // --- Test Section --- + gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 5, 4, 1); + + GtkWidget *test_label = gtk_label_new("Test Connection"); + gtk_label_set_use_markup(GTK_LABEL(test_label), TRUE); + gtk_widget_set_halign(test_label, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(grid), test_label, 0, 6, 4, 1); + + // Target Host + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target:"), 0, 7, 1, 1); + info.test_host = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(info.test_host), "google.com"); + gtk_grid_attach(GTK_GRID(grid), info.test_host, 1, 7, 1, 1); + + // Target Port + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Port:"), 2, 7, 1, 1); + info.test_port = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(info.test_port), "80"); + gtk_widget_set_size_request(info.test_port, 80, -1); + gtk_grid_attach(GTK_GRID(grid), info.test_port, 3, 7, 1, 1); + + // Start Test Button + info.test_btn = gtk_button_new_with_label("Start Test"); + g_signal_connect(info.test_btn, "clicked", G_CALLBACK(on_start_test_clicked), &info); + gtk_grid_attach(GTK_GRID(grid), info.test_btn, 0, 8, 4, 1); + + // Output Log + GtkWidget *out_scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_size_request(out_scroll, -1, 150); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(out_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + GtkWidget *out_view = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(out_view), FALSE); + gtk_text_view_set_monospace(GTK_TEXT_VIEW(out_view), TRUE); + info.output_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(out_view)); + + gtk_container_add(GTK_CONTAINER(out_scroll), out_view); + gtk_grid_attach(GTK_GRID(grid), out_scroll, 0, 9, 4, 1); + + + gtk_widget_show_all(info.dialog); + + while (TRUE) { + if (gtk_dialog_run(GTK_DIALOG(info.dialog)) != GTK_RESPONSE_ACCEPT) break; + + // Validation + const char *ip_text = gtk_entry_get_text(GTK_ENTRY(info.ip_entry)); + const char *port_text = gtk_entry_get_text(GTK_ENTRY(info.port_entry)); + + if (!ip_text || strlen(ip_text) == 0) { + show_message(GTK_WINDOW(info.dialog), GTK_MESSAGE_ERROR, "Host (IP/Domain) cannot be empty."); + continue; + } + + if (strspn(port_text, "0123456789") != strlen(port_text) || strlen(port_text) == 0) { + show_message(GTK_WINDOW(info.dialog), GTK_MESSAGE_ERROR, "Port must be a valid number."); + continue; + } + + int p = (int)safe_strtol(port_text); + if (p < 1 || p > 65535) { + show_message(GTK_WINDOW(info.dialog), GTK_MESSAGE_ERROR, "Port must be between 1 and 65535."); + continue; + } + + // Save + g_proxy_type = (gtk_combo_box_get_active(GTK_COMBO_BOX(info.type_combo)) == 0) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; + g_strlcpy(g_proxy_ip, ip_text, sizeof(g_proxy_ip)); + g_proxy_port = p; + g_strlcpy(g_proxy_user, gtk_entry_get_text(GTK_ENTRY(info.user_entry)), sizeof(g_proxy_user)); + g_strlcpy(g_proxy_pass, gtk_entry_get_text(GTK_ENTRY(info.pass_entry)), sizeof(g_proxy_pass)); + + ProxyBridge_SetProxyConfig(g_proxy_type, g_proxy_ip, g_proxy_port, g_proxy_user, g_proxy_pass); + + char status_msg[512]; + snprintf(status_msg, sizeof(status_msg), "Configuration updated: %s:%d", g_proxy_ip, g_proxy_port); + gtk_statusbar_push(GTK_STATUSBAR(status_bar), status_context_id, status_msg); + break; + } + + gtk_widget_destroy(info.dialog); +} diff --git a/Linux/gui/gui_utils.c b/Linux/gui/gui_utils.c new file mode 100644 index 0000000..a1f6931 --- /dev/null +++ b/Linux/gui/gui_utils.c @@ -0,0 +1,91 @@ +#include "gui.h" + +// Helper for safe integer conversion +long safe_strtol(const char *nptr) { + if (!nptr) return 0; // Handle NULL + char *endptr; + long val = strtol(nptr, &endptr, 10); + if (endptr == nptr) return 0; // No digits found + return val; +} + +// Helper for quick message dialogs +void show_message(GtkWindow *parent, GtkMessageType type, const char *format, ...) { + va_list args; + va_start(args, format); + char *msg = g_strdup_vprintf(format, args); + va_end(args); + + GtkWidget *dialog = gtk_message_dialog_new(parent, + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + type, + GTK_BUTTONS_OK, + "%s", msg); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_free(msg); +} + +// Helper to limit buffer lines +void trim_buffer_lines(GtkTextBuffer *buffer, int max_lines) { + if (gtk_text_buffer_get_line_count(buffer) > max_lines) { + GtkTextIter start, next; + gtk_text_buffer_get_start_iter(buffer, &start); + next = start; + gtk_text_iter_forward_line(&next); + gtk_text_buffer_delete(buffer, &start, &next); + } +} + +char* get_current_time_str() { + time_t rawtime; + struct tm *timeinfo; + char *buffer = malloc(32); + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(buffer, 32, "[%H:%M:%S]", timeinfo); + return buffer; +} + +// --- JSON Helpers --- +char *escape_json_string(const char *src) { + if (!src) return strdup(""); + GString *str = g_string_new(""); + for (const char *p = src; *p; p++) { + if (*p == '\\') g_string_append(str, "\\\\"); + else if (*p == '"') g_string_append(str, "\\\""); + else if (*p == '\n') g_string_append(str, "\\n"); + else g_string_append_c(str, *p); + } + return g_string_free(str, FALSE); +} + +// Very basic JSON parser for valid input +char *extract_sub_json_str(const char *json, const char *key) { + char search_key[256]; + snprintf(search_key, sizeof(search_key), "\"%s\"", key); + char *k = strstr(json, search_key); + if (!k) return NULL; + char *colon = strchr(k, ':'); + if (!colon) return NULL; + char *val_start = strchr(colon, '"'); + if (!val_start) return NULL; + val_start++; + char *val_end = strchr(val_start, '"'); + if (!val_end) return NULL; + return g_strndup(val_start, val_end - val_start); +} + +bool extract_sub_json_bool(const char *json, const char *key) { + char search_key[256]; + snprintf(search_key, sizeof(search_key), "\"%s\"", key); + char *k = strstr(json, search_key); + if (!k) return false; + char *colon = strchr(k, ':'); + if (!colon) return false; + // Skip spaces + char *v = colon + 1; + while(*v == ' ' || *v == '\t') v++; + if (strncmp(v, "true", 4) == 0) return true; + return false; +} diff --git a/Linux/gui/main.c b/Linux/gui/main.c index 7b9de75..aaa3294 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -1,1121 +1,22 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "ProxyBridge.h" #include "gui.h" // --- Global UI Widgets --- -static GtkWidget *window; -static GtkTextView *conn_view; -static GtkTextBuffer *conn_buffer; -static GtkTextView *log_view; -static GtkTextBuffer *log_buffer; -static GtkWidget *status_bar; -static guint status_context_id; +GtkWidget *window; +GtkTextView *conn_view; +GtkTextBuffer *conn_buffer; +GtkTextView *log_view; +GtkTextBuffer *log_buffer; +GtkWidget *status_bar; +guint status_context_id; // --- Config Globals (Defaults) --- -static char g_proxy_ip[256] = ""; -static uint16_t g_proxy_port = 0; -static ProxyType g_proxy_type = PROXY_TYPE_SOCKS5; -static char g_proxy_user[256] = ""; -static char g_proxy_pass[256] = ""; +char g_proxy_ip[256] = ""; +uint16_t g_proxy_port = 0; +ProxyType g_proxy_type = PROXY_TYPE_SOCKS5; +char g_proxy_user[256] = ""; +char g_proxy_pass[256] = ""; -static GList *g_rules_list = NULL; -static GtkWidget *rules_list_box = NULL; - -// --- Helper Functions --- - -static long safe_strtol(const char *nptr) { - if (!nptr) return 0; // Handle NULL - char *endptr; - long val = strtol(nptr, &endptr, 10); - // You could check errno here if needed, but for GUI fields 0 is often a safe fallback or filtered before - if (endptr == nptr) return 0; // No digits found - return val; -} - -// --- Helper Functions --- - -// Helper for quick message dialogs -static void show_message(GtkWindow *parent, GtkMessageType type, const char *format, ...) { - va_list args; - va_start(args, format); - char *msg = g_strdup_vprintf(format, args); - va_end(args); - - GtkWidget *dialog = gtk_message_dialog_new(parent, - GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, - type, - GTK_BUTTONS_OK, - "%s", msg); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - g_free(msg); -} - -// Helper to limit buffer lines -static void trim_buffer_lines(GtkTextBuffer *buffer, int max_lines) { - if (gtk_text_buffer_get_line_count(buffer) > max_lines) { - GtkTextIter start, next; - gtk_text_buffer_get_start_iter(buffer, &start); - next = start; - gtk_text_iter_forward_line(&next); - gtk_text_buffer_delete(buffer, &start, &next); - } -} - -// Forward declaration -static void refresh_rules_ui(); - -static void free_rule_data(RuleData *rule) { - if (rule) { - if (rule->process_name) free(rule->process_name); - if (rule->target_hosts) free(rule->target_hosts); - if (rule->target_ports) free(rule->target_ports); - free(rule); - } -} - -static char* get_current_time_str() { - time_t rawtime; - struct tm *timeinfo; - char *buffer = malloc(32); - time(&rawtime); - timeinfo = localtime(&rawtime); - strftime(buffer, 32, "[%H:%M:%S]", timeinfo); - return buffer; -} - -static void free_connection_data(ConnectionData *data) { - if (data) { - free(data->process_name); - free(data->dest_ip); - free(data->proxy_info); - free(data->timestamp); - free(data); - } -} - -// --- GTK Callbacks (Main Thread) --- - -// Filter Function for TextView -static void filter_text_view(GtkTextBuffer *buffer, const char *text) { - if (!buffer) return; - - GtkTextIter start, end; - gtk_text_buffer_get_bounds(buffer, &start, &end); - gtk_text_buffer_remove_tag_by_name(buffer, "hidden", &start, &end); - - if (!text || strlen(text) == 0) return; - - GtkTextIter line_start = start; - while (!gtk_text_iter_is_end(&line_start)) { - GtkTextIter line_end = line_start; - if (!gtk_text_iter_ends_line(&line_end)) - gtk_text_iter_forward_to_line_end(&line_end); - - char *line_text = gtk_text_buffer_get_text(buffer, &line_start, &line_end, FALSE); - - // Case-insensitive search - char *lower_line = g_utf8_strdown(line_text, -1); - char *lower_search = g_utf8_strdown(text, -1); - - if (!strstr(lower_line, lower_search)) { - GtkTextIter next_line = line_end; - gtk_text_iter_forward_char(&next_line); // include newline - gtk_text_buffer_apply_tag_by_name(buffer, "hidden", &line_start, &next_line); - } - - g_free(lower_line); - g_free(lower_search); - g_free(line_text); - - gtk_text_iter_forward_line(&line_start); - } -} - -static void on_search_conn_changed(GtkSearchEntry *entry, gpointer user_data) { - const char *text = gtk_entry_get_text(GTK_ENTRY(entry)); - filter_text_view(conn_buffer, text); -} - -static void on_search_log_changed(GtkSearchEntry *entry, gpointer user_data) { - const char *text = gtk_entry_get_text(GTK_ENTRY(entry)); - filter_text_view(log_buffer, text); -} - -static void on_clear_conn_clicked(GtkButton *button, gpointer user_data) { - if (conn_buffer) gtk_text_buffer_set_text(conn_buffer, "", 0); -} - -static void on_clear_log_clicked(GtkButton *button, gpointer user_data) { - if (log_buffer) gtk_text_buffer_set_text(log_buffer, "", 0); -} - -// Thread-safe idle callback for Log -static gboolean update_log_gui(gpointer user_data) { - LogData *data = (LogData *)user_data; - if (!data) return FALSE; - - GtkTextIter end; - gtk_text_buffer_get_end_iter(log_buffer, &end); - - char full_msg[1200]; - snprintf(full_msg, sizeof(full_msg), "%s %s\n", get_current_time_str(), data->message); - gtk_text_buffer_insert(log_buffer, &end, full_msg, -1); - - // Limit to 100 lines to prevent memory growth - trim_buffer_lines(log_buffer, 100); - - free(data->message); - free(data); - return FALSE; // Remove source -} - -// Thread-safe idle callback for Connection -static gboolean update_connection_gui_append(gpointer user_data) { - ConnectionData *data = (ConnectionData *)user_data; - if (!data) return FALSE; - - if (conn_buffer) { - GtkTextIter end; - gtk_text_buffer_get_end_iter(conn_buffer, &end); - - char line_buffer[1024]; - // Format: [Time] Process (PID) -> Target via ProxyInfo - snprintf(line_buffer, sizeof(line_buffer), "%s %s (PID:%u) -> %s:%u via %s\n", - data->timestamp, data->process_name, data->pid, data->dest_ip, data->dest_port, data->proxy_info); - - gtk_text_buffer_insert(conn_buffer, &end, line_buffer, -1); - - // Limit to 100 lines to prevent memory growth - trim_buffer_lines(conn_buffer, 100); - } - - free_connection_data(data); - return FALSE; -} - - -// --- Library Callbacks --- - -static void lib_log_callback(const char *message) { - LogData *data = malloc(sizeof(LogData)); - data->message = strdup(message); - g_idle_add(update_log_gui, data); -} - -static void lib_connection_callback(const char *process_name, uint32_t pid, const char *dest_ip, uint16_t dest_port, const char *proxy_info) { - ConnectionData *data = malloc(sizeof(ConnectionData)); - data->process_name = strdup(process_name); - data->pid = pid; - data->dest_ip = strdup(dest_ip); - data->dest_port = dest_port; - data->proxy_info = strdup(proxy_info); - data->timestamp = get_current_time_str(); - g_idle_add(update_connection_gui_append, data); -} - -// --- Settings Dialog --- - -static gboolean on_test_done(gpointer user_data) { - TestResultData *data = (TestResultData *)user_data; - gtk_text_buffer_set_text(data->buffer, data->result_text, -1); - gtk_widget_set_sensitive(data->btn, TRUE); - - free(data->result_text); - free(data); - return FALSE; -} - -static gpointer run_test_thread(gpointer user_data) { - struct TestRunnerData *req = (struct TestRunnerData *)user_data; - - char *buffer = malloc(4096); - memset(buffer, 0, 4096); - - // Check callback if needed, but TestConnection usually returns result in buffer - ProxyBridge_TestConnection(req->host, req->port, buffer, 4096); - - TestResultData *res = malloc(sizeof(TestResultData)); - res->result_text = buffer; - res->buffer = req->ui_info->output_buffer; - res->btn = req->ui_info->test_btn; - - g_idle_add(on_test_done, res); - - free(req->host); - free(req); - return NULL; -} - -static void on_start_test_clicked(GtkWidget *widget, gpointer data) { - ConfigInfo *info = (ConfigInfo *)data; - - // 1. Validate Proxy Config - const char *ip_text = gtk_entry_get_text(GTK_ENTRY(info->ip_entry)); - const char *port_text = gtk_entry_get_text(GTK_ENTRY(info->port_entry)); - - if (!ip_text || strlen(ip_text) == 0 || strspn(port_text, "0123456789") != strlen(port_text) || strlen(port_text) == 0) { - gtk_text_buffer_set_text(info->output_buffer, "Error: Invalid Proxy IP or Port.", -1); - return; - } - - // 2. Set Proxy Config - ProxyType type = (gtk_combo_box_get_active(GTK_COMBO_BOX(info->type_combo)) == 0) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; - int port = (int)safe_strtol(port_text); - const char *user = gtk_entry_get_text(GTK_ENTRY(info->user_entry)); - const char *pass = gtk_entry_get_text(GTK_ENTRY(info->pass_entry)); - - ProxyBridge_SetProxyConfig(type, ip_text, port, user, pass); - - // 3. Get Test Target - const char *t_host = gtk_entry_get_text(GTK_ENTRY(info->test_host)); - const char *t_port_s = gtk_entry_get_text(GTK_ENTRY(info->test_port)); - - if (!t_host || strlen(t_host) == 0) t_host = "google.com"; - int t_port = (int)safe_strtol(t_port_s); - if (t_port <= 0) t_port = 80; - - // 4. Update UI - gtk_text_buffer_set_text(info->output_buffer, "Testing connection... Please wait...", -1); - gtk_widget_set_sensitive(info->test_btn, FALSE); - - // 5. Run Thread - struct TestRunnerData *req = malloc(sizeof(struct TestRunnerData)); - req->host = strdup(t_host); - req->port = t_port; - req->ui_info = info; - - GThread *thread = g_thread_new("test_conn", run_test_thread, req); - g_thread_unref(thread); -} - -static void on_proxy_configure(GtkWidget *widget, gpointer data) { - ConfigInfo info; // We will pass address to signals, careful about scope if dialog is non-modal? No, dialog_run is blocking. - // Wait, on_start_test_clicked will be called while dialog_run is blocking main loop? Yes. - // But info needs to persist. Stack allocation is fine because we don't leave this function until dialog closes. - - GtkWidget *content_area; - GtkWidget *grid; - // Removed local declarations that are now in struct ConfigInfo - - info.dialog = gtk_dialog_new_with_buttons("Proxy Settings", - GTK_WINDOW(window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - "Cancel", GTK_RESPONSE_CANCEL, - "Save", GTK_RESPONSE_ACCEPT, - NULL); - // Increase width for log - gtk_window_set_default_size(GTK_WINDOW(info.dialog), 600, 500); - - content_area = gtk_dialog_get_content_area(GTK_DIALOG(info.dialog)); - grid = gtk_grid_new(); - gtk_grid_set_row_spacing(GTK_GRID(grid), 5); - gtk_grid_set_column_spacing(GTK_GRID(grid), 10); - gtk_container_set_border_width(GTK_CONTAINER(grid), 10); - gtk_box_pack_start(GTK_BOX(content_area), grid, TRUE, TRUE, 0); - - // Type - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Type:"), 0, 0, 1, 1); - info.type_combo = gtk_combo_box_text_new(); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(info.type_combo), "HTTP"); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(info.type_combo), "SOCKS5"); - gtk_combo_box_set_active(GTK_COMBO_BOX(info.type_combo), g_proxy_type == PROXY_TYPE_HTTP ? 0 : 1); - gtk_grid_attach(GTK_GRID(grid), info.type_combo, 1, 0, 3, 1); // Span 3 - - // IP - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Host:"), 0, 1, 1, 1); - info.ip_entry = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(info.ip_entry), g_proxy_ip); - gtk_widget_set_hexpand(info.ip_entry, TRUE); - gtk_grid_attach(GTK_GRID(grid), info.ip_entry, 1, 1, 3, 1); - - // Port - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Port:"), 0, 2, 1, 1); - info.port_entry = gtk_entry_new(); - if (g_proxy_port != 0) { - char port_str[16]; - snprintf(port_str, sizeof(port_str), "%d", g_proxy_port); - gtk_entry_set_text(GTK_ENTRY(info.port_entry), port_str); - } - gtk_grid_attach(GTK_GRID(grid), info.port_entry, 1, 2, 3, 1); - - // User - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Username:"), 0, 3, 1, 1); - info.user_entry = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(info.user_entry), g_proxy_user); - gtk_grid_attach(GTK_GRID(grid), info.user_entry, 1, 3, 3, 1); - - // Pass - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Password:"), 0, 4, 1, 1); - info.pass_entry = gtk_entry_new(); - gtk_entry_set_visibility(GTK_ENTRY(info.pass_entry), FALSE); - gtk_entry_set_text(GTK_ENTRY(info.pass_entry), g_proxy_pass); - gtk_grid_attach(GTK_GRID(grid), info.pass_entry, 1, 4, 3, 1); - - // --- Test Section --- - gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 5, 4, 1); - - GtkWidget *test_label = gtk_label_new("Test Connection"); - gtk_label_set_use_markup(GTK_LABEL(test_label), TRUE); - gtk_widget_set_halign(test_label, GTK_ALIGN_START); - gtk_grid_attach(GTK_GRID(grid), test_label, 0, 6, 4, 1); - - // Target Host - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target:"), 0, 7, 1, 1); - info.test_host = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(info.test_host), "google.com"); - gtk_grid_attach(GTK_GRID(grid), info.test_host, 1, 7, 1, 1); - - // Target Port - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Port:"), 2, 7, 1, 1); - info.test_port = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(info.test_port), "80"); - gtk_widget_set_size_request(info.test_port, 80, -1); - gtk_grid_attach(GTK_GRID(grid), info.test_port, 3, 7, 1, 1); - - // Start Test Button - info.test_btn = gtk_button_new_with_label("Start Test"); - g_signal_connect(info.test_btn, "clicked", G_CALLBACK(on_start_test_clicked), &info); - gtk_grid_attach(GTK_GRID(grid), info.test_btn, 0, 8, 4, 1); - - // Output Log - GtkWidget *out_scroll = gtk_scrolled_window_new(NULL, NULL); - gtk_widget_set_size_request(out_scroll, -1, 150); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(out_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - - GtkWidget *out_view = gtk_text_view_new(); - gtk_text_view_set_editable(GTK_TEXT_VIEW(out_view), FALSE); - gtk_text_view_set_monospace(GTK_TEXT_VIEW(out_view), TRUE); - info.output_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(out_view)); - - gtk_container_add(GTK_CONTAINER(out_scroll), out_view); - gtk_grid_attach(GTK_GRID(grid), out_scroll, 0, 9, 4, 1); - - - gtk_widget_show_all(info.dialog); - - while (TRUE) { - if (gtk_dialog_run(GTK_DIALOG(info.dialog)) != GTK_RESPONSE_ACCEPT) break; - - // Validation - const char *ip_text = gtk_entry_get_text(GTK_ENTRY(info.ip_entry)); - const char *port_text = gtk_entry_get_text(GTK_ENTRY(info.port_entry)); - - if (!ip_text || strlen(ip_text) == 0) { - show_message(GTK_WINDOW(info.dialog), GTK_MESSAGE_ERROR, "Host (IP/Domain) cannot be empty."); - continue; - } - - if (strspn(port_text, "0123456789") != strlen(port_text) || strlen(port_text) == 0) { - show_message(GTK_WINDOW(info.dialog), GTK_MESSAGE_ERROR, "Port must be a valid number."); - continue; - } - - int p = (int)safe_strtol(port_text); - if (p < 1 || p > 65535) { - show_message(GTK_WINDOW(info.dialog), GTK_MESSAGE_ERROR, "Port must be between 1 and 65535."); - continue; - } - - // Save - g_proxy_type = (gtk_combo_box_get_active(GTK_COMBO_BOX(info.type_combo)) == 0) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; - strncpy(g_proxy_ip, ip_text, sizeof(g_proxy_ip)-1); - g_proxy_port = p; - strncpy(g_proxy_user, gtk_entry_get_text(GTK_ENTRY(info.user_entry)), sizeof(g_proxy_user)-1); - strncpy(g_proxy_pass, gtk_entry_get_text(GTK_ENTRY(info.pass_entry)), sizeof(g_proxy_pass)-1); - - ProxyBridge_SetProxyConfig(g_proxy_type, g_proxy_ip, g_proxy_port, g_proxy_user, g_proxy_pass); - - char status_msg[512]; - snprintf(status_msg, sizeof(status_msg), "Configuration updated: %s:%d", g_proxy_ip, g_proxy_port); - gtk_statusbar_push(GTK_STATUSBAR(status_bar), status_context_id, status_msg); - break; - } - - gtk_widget_destroy(info.dialog); -} - -// --- Rules Management --- - -static void on_rule_delete(GtkWidget *widget, gpointer data) { - RuleData *rule = (RuleData *)data; - - // Call C API - ProxyBridge_DeleteRule(rule->id); - - // Remove from list - g_rules_list = g_list_remove(g_rules_list, rule); - free_rule_data(rule); - - // UI Refresh - refresh_rules_ui(); -} - -static void on_rule_toggle(GtkToggleButton *btn, gpointer data) { - RuleData *rule = (RuleData *)data; - rule->enabled = gtk_toggle_button_get_active(btn); - - if (rule->enabled) { - ProxyBridge_EnableRule(rule->id); - } else { - ProxyBridge_DisableRule(rule->id); - } -} - -static void on_save_rule(GtkWidget *widget, gpointer data) { - GtkWidget **widgets = (GtkWidget **)data; - GtkWidget *dialog = widgets[0]; - RuleData *edit_rule = (RuleData *)widgets[6]; // If not null, we are editing - - const char *proc = gtk_entry_get_text(GTK_ENTRY(widgets[1])); - const char *hosts = gtk_entry_get_text(GTK_ENTRY(widgets[2])); - const char *ports = gtk_entry_get_text(GTK_ENTRY(widgets[3])); - RuleProtocol proto = gtk_combo_box_get_active(GTK_COMBO_BOX(widgets[4])); - RuleAction action = gtk_combo_box_get_active(GTK_COMBO_BOX(widgets[5])); - - if (strlen(proc) == 0) { - // Error - return; - } - - if (edit_rule) { - // Edit Existing - ProxyBridge_EditRule(edit_rule->id, proc, hosts, ports, proto, action); - - // Update Local - free(edit_rule->process_name); edit_rule->process_name = strdup(proc); - free(edit_rule->target_hosts); edit_rule->target_hosts = strdup(hosts); - free(edit_rule->target_ports); edit_rule->target_ports = strdup(ports); - edit_rule->protocol = proto; - edit_rule->action = action; - - } else { - // Add New - uint32_t new_id = ProxyBridge_AddRule(proc, hosts, ports, proto, action); - - RuleData *new_rule = malloc(sizeof(RuleData)); - new_rule->id = new_id; - new_rule->process_name = strdup(proc); - new_rule->target_hosts = strdup(hosts); - new_rule->target_ports = strdup(ports); - new_rule->protocol = proto; - new_rule->action = action; - new_rule->enabled = true; // Default enabled - new_rule->selected = false; - - g_rules_list = g_list_append(g_rules_list, new_rule); - } - - refresh_rules_ui(); - gtk_widget_destroy(dialog); - free(widgets); -} - -static void on_browse_clicked(GtkWidget *widget, gpointer data) { - GtkWidget *entry = (GtkWidget *)data; - GtkWidget *dialog = gtk_file_chooser_dialog_new("Select Application", - NULL, - GTK_FILE_CHOOSER_ACTION_OPEN, - "_Cancel", GTK_RESPONSE_CANCEL, - "_Select", GTK_RESPONSE_ACCEPT, - NULL); - - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - char *base = g_path_get_basename(filename); - gtk_entry_set_text(GTK_ENTRY(entry), base); - g_free(base); - g_free(filename); - } - gtk_widget_destroy(dialog); -} - -static void open_rule_dialog(RuleData *rule) { - GtkWidget *dialog = gtk_dialog_new(); - gtk_window_set_title(GTK_WINDOW(dialog), rule ? "Edit Rule" : "Add Rule"); - gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window)); - gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); - gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 400); - - GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - GtkWidget *grid = gtk_grid_new(); - gtk_grid_set_row_spacing(GTK_GRID(grid), 8); - gtk_grid_set_column_spacing(GTK_GRID(grid), 10); - gtk_container_set_border_width(GTK_CONTAINER(grid), 15); - - // Process - GtkWidget *proc_entry = gtk_entry_new(); - GtkWidget *browse_btn = gtk_button_new_with_label("Browse..."); - g_signal_connect(browse_btn, "clicked", G_CALLBACK(on_browse_clicked), proc_entry); - - GtkWidget *proc_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); - gtk_box_pack_start(GTK_BOX(proc_box), proc_entry, TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(proc_box), browse_btn, FALSE, FALSE, 0); - - GtkWidget *host_entry = gtk_entry_new(); - GtkWidget *port_entry = gtk_entry_new(); - - // Protocol - GtkWidget *proto_combo = gtk_combo_box_text_new(); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "TCP"); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "UDP"); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "BOTH"); - - // Action - GtkWidget *action_combo = gtk_combo_box_text_new(); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "PROXY"); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "DIRECT"); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "BLOCK"); - - if (rule) { - gtk_entry_set_text(GTK_ENTRY(proc_entry), rule->process_name); - gtk_entry_set_text(GTK_ENTRY(host_entry), rule->target_hosts); - gtk_entry_set_text(GTK_ENTRY(port_entry), rule->target_ports); - gtk_combo_box_set_active(GTK_COMBO_BOX(proto_combo), rule->protocol); - gtk_combo_box_set_active(GTK_COMBO_BOX(action_combo), rule->action); - } else { - gtk_entry_set_text(GTK_ENTRY(port_entry), "*"); // Default - gtk_entry_set_text(GTK_ENTRY(host_entry), "*"); // Default - gtk_combo_box_set_active(GTK_COMBO_BOX(proto_combo), 2); // BOTH - gtk_combo_box_set_active(GTK_COMBO_BOX(action_combo), 0); // PROXY - } - - int row = 0; - - // Process Row - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Process Name:"), 0, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), proc_box, 1, row, 1, 1); - row++; - - // Process Hint - GtkWidget *proc_hint = gtk_label_new("Example: firefox; chrome; /usr/bin/wget"); - gtk_style_context_add_class(gtk_widget_get_style_context(proc_hint), "dim-label"); - gtk_widget_set_halign(proc_hint, GTK_ALIGN_START); - gtk_grid_attach(GTK_GRID(grid), proc_hint, 1, row, 1, 1); - row++; - - // Host Row - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target Host:"), 0, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), host_entry, 1, row, 1, 1); - row++; - - // Host Hint - GtkWidget *host_hint = gtk_label_new("Example: 192.168.1.*; 10.0.0.1-50; *"); - gtk_style_context_add_class(gtk_widget_get_style_context(host_hint), "dim-label"); - gtk_widget_set_halign(host_hint, GTK_ALIGN_START); - gtk_grid_attach(GTK_GRID(grid), host_hint, 1, row, 1, 1); - row++; - - // Port Row - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target Port:"), 0, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), port_entry, 1, row, 1, 1); - row++; - - // Port Hint - GtkWidget *port_hint = gtk_label_new("Example: 80; 443; 8000-8080; *"); - gtk_style_context_add_class(gtk_widget_get_style_context(port_hint), "dim-label"); - gtk_widget_set_halign(port_hint, GTK_ALIGN_START); - gtk_grid_attach(GTK_GRID(grid), port_hint, 1, row, 1, 1); - row++; - - // Protocol - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Protocol:"), 0, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), proto_combo, 1, row, 1, 1); - row++; - - // Action - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Action:"), 0, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), action_combo, 1, row, 1, 1); - row++; - - gtk_container_add(GTK_CONTAINER(content), grid); - - GtkWidget *save_btn = gtk_button_new_with_label("Save"); - GtkWidget *cancel_btn = gtk_button_new_with_label("Cancel"); - gtk_dialog_add_action_widget(GTK_DIALOG(dialog), cancel_btn, GTK_RESPONSE_CANCEL); - gtk_dialog_add_action_widget(GTK_DIALOG(dialog), save_btn, GTK_RESPONSE_ACCEPT); - - // Pass everything needed to callback - GtkWidget **data = malloc(7 * sizeof(GtkWidget*)); - data[0] = dialog; - data[1] = proc_entry; - data[2] = host_entry; - data[3] = port_entry; - data[4] = proto_combo; - data[5] = action_combo; - data[6] = (GtkWidget*)rule; // Hacky cast - - g_signal_connect(save_btn, "clicked", G_CALLBACK(on_save_rule), data); - g_signal_connect(cancel_btn, "clicked", G_CALLBACK(gtk_widget_destroy), NULL); - // Simplified for now. - - gtk_widget_show_all(dialog); -} - -static void on_rule_edit(GtkWidget *widget, gpointer data) { - RuleData *rule = (RuleData *)data; - open_rule_dialog(rule); -} - -static void on_rule_add_clicked(GtkWidget *widget, gpointer data) { - open_rule_dialog(NULL); -} - -static GtkWidget *btn_select_all_header = NULL; // Renamed to separate from any local vars - -// Note: rules_list_box is already defined at top of file, so we just use it -static void refresh_rules_ui(); // Forward decl - -static void on_rule_select_toggle(GtkToggleButton *btn, gpointer data) { - RuleData *rule = (RuleData *)data; - rule->selected = gtk_toggle_button_get_active(btn); - // Refresh only the Select All button label - if (btn_select_all_header) { - bool all_selected = (g_rules_list != NULL); - if (g_rules_list == NULL) all_selected = false; - for (GList *l = g_rules_list; l != NULL; l = l->next) { - RuleData *r = (RuleData *)l->data; - if (!r->selected) { - all_selected = false; - break; - } - } - gtk_button_set_label(GTK_BUTTON(btn_select_all_header), all_selected ? "Deselect All" : "Select All"); - } -} - -// --- JSON Helpers --- -static char *escape_json_string(const char *src) { - if (!src) return strdup(""); - GString *str = g_string_new(""); - for (const char *p = src; *p; p++) { - if (*p == '\\') g_string_append(str, "\\\\"); - else if (*p == '"') g_string_append(str, "\\\""); - else if (*p == '\n') g_string_append(str, "\\n"); - else g_string_append_c(str, *p); - } - return g_string_free(str, FALSE); -} - -// Very basic JSON parser for valid input -static char *extract_sub_json_str(const char *json, const char *key) { - char search_key[256]; - snprintf(search_key, sizeof(search_key), "\"%s\"", key); - char *k = strstr(json, search_key); - if (!k) return NULL; - char *colon = strchr(k, ':'); - if (!colon) return NULL; - char *val_start = strchr(colon, '"'); - if (!val_start) return NULL; - val_start++; - char *val_end = strchr(val_start, '"'); - if (!val_end) return NULL; - return g_strndup(val_start, val_end - val_start); -} - -static bool extract_sub_json_bool(const char *json, const char *key) { - char search_key[256]; - snprintf(search_key, sizeof(search_key), "\"%s\"", key); - char *k = strstr(json, search_key); - if (!k) return false; - char *colon = strchr(k, ':'); - if (!colon) return false; - // Skip spaces - char *v = colon + 1; - while(*v == ' ' || *v == '\t') v++; - if (strncmp(v, "true", 4) == 0) return true; - return false; -} - -static void on_rule_export_clicked(GtkWidget *widget, gpointer data) { - if (!g_rules_list) return; - - // Check if any selected - bool any_selected = false; - for (GList *l = g_rules_list; l != NULL; l = l->next) { - if (((RuleData *)l->data)->selected) { any_selected = true; break; } - } - if (!any_selected) { - show_message(NULL, GTK_MESSAGE_WARNING, "Please select at least one rule to export."); - return; - } - - GtkWidget *dialog = gtk_file_chooser_dialog_new("Export Rules", - GTK_WINDOW(window), - GTK_FILE_CHOOSER_ACTION_SAVE, - "_Cancel", GTK_RESPONSE_CANCEL, - "_Save", GTK_RESPONSE_ACCEPT, - NULL); - gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); - gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "proxy_rules.json"); - - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - FILE *f = fopen(filename, "w"); - if (f) { - fprintf(f, "[\n"); - bool first = true; - for (GList *l = g_rules_list; l != NULL; l = l->next) { - RuleData *r = (RuleData *)l->data; - if (!r->selected) continue; - - if (!first) fprintf(f, ",\n"); - char *proc = escape_json_string(r->process_name); - char *host = escape_json_string(r->target_hosts); - char *port = escape_json_string(r->target_ports); - const char *proto = (r->protocol == RULE_PROTOCOL_TCP) ? "TCP" : (r->protocol == RULE_PROTOCOL_UDP ? "UDP" : "BOTH"); - const char *act = (r->action == RULE_ACTION_PROXY) ? "PROXY" : (r->action == RULE_ACTION_DIRECT ? "DIRECT" : "BLOCK"); - - fprintf(f, " {\n"); - fprintf(f, " \"processNames\": \"%s\",\n", proc); - fprintf(f, " \"targetHosts\": \"%s\",\n", host); - fprintf(f, " \"targetPorts\": \"%s\",\n", port); - fprintf(f, " \"protocol\": \"%s\",\n", proto); - fprintf(f, " \"action\": \"%s\",\n", act); - fprintf(f, " \"enabled\": %s\n", r->enabled ? "true" : "false"); - fprintf(f, " }"); - - g_free(proc); g_free(host); g_free(port); - first = false; - } - fprintf(f, "\n]\n"); - fclose(f); - - show_message(NULL, GTK_MESSAGE_INFO, "Rules exported successfully."); - } - g_free(filename); - } - gtk_widget_destroy(dialog); -} - -static void on_rule_import_clicked(GtkWidget *widget, gpointer data) { - GtkWidget *dialog = gtk_file_chooser_dialog_new("Import Rules", - GTK_WINDOW(window), - GTK_FILE_CHOOSER_ACTION_OPEN, - "_Cancel", GTK_RESPONSE_CANCEL, - "_Open", GTK_RESPONSE_ACCEPT, - NULL); - - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - char *content = NULL; - gsize len; - - if (g_file_get_contents(filename, &content, &len, NULL)) { - // Simple robust scan: look for { ... } blocks - char *curr = content; - int imported = 0; - while ((curr = strchr(curr, '{')) != NULL) { - char *end = strchr(curr, '}'); - if (!end) break; - - // Temp terminate to limit search scope - char saved = *end; - *end = '\0'; - - // Parse - char *proc = extract_sub_json_str(curr, "processNames"); - char *host = extract_sub_json_str(curr, "targetHosts"); - char *port = extract_sub_json_str(curr, "targetPorts"); - char *proto_s = extract_sub_json_str(curr, "protocol"); - char *act_s = extract_sub_json_str(curr, "action"); - bool en = extract_sub_json_bool(curr, "enabled"); - - if (proc && host && port && proto_s && act_s) { - RuleProtocol p = RULE_PROTOCOL_BOTH; - if (strcmp(proto_s, "TCP") == 0) p = RULE_PROTOCOL_TCP; - else if (strcmp(proto_s, "UDP") == 0) p = RULE_PROTOCOL_UDP; - - RuleAction a = RULE_ACTION_PROXY; - if (strcmp(act_s, "DIRECT") == 0) a = RULE_ACTION_DIRECT; - else if (strcmp(act_s, "BLOCK") == 0) a = RULE_ACTION_BLOCK; - - uint32_t nid = ProxyBridge_AddRule(proc, host, port, p, a); - // Update enabled if needed (AddRule creates enabled by default, but check ID) - if (!en) ProxyBridge_DisableRule(nid); - - // Add to UI list struct - RuleData *nd = malloc(sizeof(RuleData)); - nd->id = nid; - nd->process_name = strdup(proc); - nd->target_hosts = strdup(host); - nd->target_ports = strdup(port); - nd->protocol = p; - nd->action = a; - nd->enabled = en; - nd->selected = false; - - g_rules_list = g_list_append(g_rules_list, nd); - imported++; - } - - g_free(proc); g_free(host); g_free(port); g_free(proto_s); g_free(act_s); - - *end = saved; // Restore - curr = end + 1; - } - g_free(content); - - if (imported > 0) { - refresh_rules_ui(); - show_message(NULL, GTK_MESSAGE_INFO, "Imported %d rules.", imported); - } - } - g_free(filename); - } - gtk_widget_destroy(dialog); -} - -static void on_bulk_delete_clicked(GtkWidget *widget, gpointer data) { - if (!g_rules_list) return; - - GList *iter = g_rules_list; - // Collect to-delete items first - GList *to_delete = NULL; - - while (iter != NULL) { - RuleData *rule = (RuleData *)iter->data; - if (rule->selected) { - to_delete = g_list_append(to_delete, rule); - } - iter = iter->next; - } - - if (!to_delete) return; // Nothing to delete - - // Delete them - for (GList *d = to_delete; d != NULL; d = d->next) { - RuleData *rule = (RuleData *)d->data; - ProxyBridge_DeleteRule(rule->id); - g_rules_list = g_list_remove(g_rules_list, rule); - free_rule_data(rule); - } - g_list_free(to_delete); - - refresh_rules_ui(); -} - -static void on_select_all_clicked(GtkWidget *widget, gpointer data) { - if (!g_rules_list) return; - - // Check if currently all selected - bool all_selected = true; - for (GList *l = g_rules_list; l != NULL; l = l->next) { - RuleData *r = (RuleData *)l->data; - if (!r->selected) { - all_selected = false; - break; - } - } - - bool new_state = !all_selected; // Toggle - - for (GList *l = g_rules_list; l != NULL; l = l->next) { - RuleData *r = (RuleData *)l->data; - r->selected = new_state; - } - refresh_rules_ui(); -} - -static void refresh_rules_ui() { - if (!rules_list_box) return; - - // Clear existing - GList *children, *iter; - children = gtk_container_get_children(GTK_CONTAINER(rules_list_box)); - for(iter = children; iter != NULL; iter = g_list_next(iter)) - gtk_widget_destroy(GTK_WIDGET(iter->data)); - g_list_free(children); - - // Check All status for button label - if (btn_select_all_header) { - bool all_selected = (g_rules_list != NULL); - if (g_rules_list == NULL) all_selected = false; - - for (GList *l = g_rules_list; l != NULL; l = l->next) { - RuleData *r = (RuleData *)l->data; - if (!r->selected) { - all_selected = false; - break; - } - } - gtk_button_set_label(GTK_BUTTON(btn_select_all_header), all_selected ? "Deselect All" : "Select All"); - } - - // Use GtkGrid for alignment - GtkWidget *grid = gtk_grid_new(); - gtk_grid_set_row_spacing(GTK_GRID(grid), 10); - gtk_grid_set_column_spacing(GTK_GRID(grid), 15); - gtk_container_add(GTK_CONTAINER(rules_list_box), grid); - - // Headers - // Col 0: Select (new) - // Col 1: Enabled - // Col 2: Actions - // Col 3: SR - // Col 4: Process - // Col 5: Host - // Col 6: Protocol - // Col 7: Action - - int row = 0; - gtk_grid_attach(GTK_GRID(grid), gtk_label_new(" "), 0, row, 1, 1); // Selection Header - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Enable"), 1, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Actions"), 2, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("SR"), 3, row, 1, 1); - - GtkWidget *h_proc = gtk_label_new("Process"); gtk_widget_set_halign(h_proc, GTK_ALIGN_START); - gtk_widget_set_hexpand(h_proc, TRUE); - gtk_grid_attach(GTK_GRID(grid), h_proc, 4, row, 1, 1); - - GtkWidget *h_host = gtk_label_new("Target Hosts"); gtk_widget_set_halign(h_host, GTK_ALIGN_START); - gtk_widget_set_hexpand(h_host, TRUE); - gtk_grid_attach(GTK_GRID(grid), h_host, 5, row, 1, 1); - - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Protocol"), 6, row, 1, 1); - gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Action"), 7, row, 1, 1); - - // Separator - row++; - gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 8, 1); - row++; - - // Data Rows - int sr_counter = 1; - for (GList *l = g_rules_list; l != NULL; l = l->next) { - RuleData *r = (RuleData *)l->data; - - // Select Checkbox - GtkWidget *chk_sel = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk_sel), r->selected); - g_signal_connect(chk_sel, "toggled", G_CALLBACK(on_rule_select_toggle), r); - gtk_grid_attach(GTK_GRID(grid), chk_sel, 0, row, 1, 1); - - // Enabled - GtkWidget *chk = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk), r->enabled); - g_signal_connect(chk, "toggled", G_CALLBACK(on_rule_toggle), r); - gtk_grid_attach(GTK_GRID(grid), chk, 1, row, 1, 1); - - // Actions - GtkWidget *act_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2); - GtkWidget *btn_edit = gtk_button_new_with_label("Edit"); - g_signal_connect(btn_edit, "clicked", G_CALLBACK(on_rule_edit), r); - GtkWidget *btn_del = gtk_button_new_with_label("Delete"); - g_signal_connect(btn_del, "clicked", G_CALLBACK(on_rule_delete), r); - gtk_box_pack_start(GTK_BOX(act_box), btn_edit, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(act_box), btn_del, FALSE, FALSE, 0); - gtk_grid_attach(GTK_GRID(grid), act_box, 2, row, 1, 1); - - // SR - char sr_str[16]; snprintf(sr_str, sizeof(sr_str), "%d", sr_counter++); - gtk_grid_attach(GTK_GRID(grid), gtk_label_new(sr_str), 3, row, 1, 1); - - // Process - GtkWidget *l_proc = gtk_label_new(r->process_name); - gtk_widget_set_halign(l_proc, GTK_ALIGN_START); - gtk_label_set_ellipsize(GTK_LABEL(l_proc), PANGO_ELLIPSIZE_END); - gtk_grid_attach(GTK_GRID(grid), l_proc, 4, row, 1, 1); - - // Host - GtkWidget *l_host = gtk_label_new(r->target_hosts); - gtk_widget_set_halign(l_host, GTK_ALIGN_START); - gtk_label_set_ellipsize(GTK_LABEL(l_host), PANGO_ELLIPSIZE_END); - gtk_grid_attach(GTK_GRID(grid), l_host, 5, row, 1, 1); - - // Protocol - const char* proto_strs[] = {"TCP", "UDP", "BOTH"}; - gtk_grid_attach(GTK_GRID(grid), gtk_label_new(proto_strs[r->protocol]), 6, row, 1, 1); - - // Action - const char* action_strs[] = {"PROXY", "DIRECT", "BLOCK"}; - GtkWidget *l_act = gtk_label_new(action_strs[r->action]); - // Set Color - GtkStyleContext *context = gtk_widget_get_style_context(l_act); - if (r->action == RULE_ACTION_PROXY) gtk_style_context_add_class(context, "success"); - else if (r->action == RULE_ACTION_DIRECT) gtk_style_context_add_class(context, "info"); - else gtk_style_context_add_class(context, "warning"); - - gtk_grid_attach(GTK_GRID(grid), l_act, 7, row, 1, 1); - - row++; - gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 8, 1); - row++; - } - - gtk_widget_show_all(rules_list_box); -} - -static void on_proxy_rules_clicked(GtkWidget *widget, gpointer data) { - GtkWidget *dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(dialog), "Process Rules"); - gtk_window_set_default_size(GTK_WINDOW(dialog), 800, 500); - gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window)); - - GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); - gtk_container_set_border_width(GTK_CONTAINER(vbox), 20); - gtk_container_add(GTK_CONTAINER(dialog), vbox); - - // Header Row with Title and Add Button - GtkWidget *header_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - GtkWidget *title = gtk_label_new(NULL); - gtk_label_set_markup(GTK_LABEL(title), "Process Rules"); - - GtkWidget *add_btn = gtk_button_new_with_label("+ Add Rule"); - g_signal_connect(add_btn, "clicked", G_CALLBACK(on_rule_add_clicked), NULL); - - // Select All Button - btn_select_all_header = gtk_button_new_with_label("Select All"); - g_signal_connect(btn_select_all_header, "clicked", G_CALLBACK(on_select_all_clicked), NULL); - - // Bulk Delete Button - GtkWidget *del_all_btn = gtk_button_new_with_label("Delete Selected"); - g_signal_connect(del_all_btn, "clicked", G_CALLBACK(on_bulk_delete_clicked), NULL); - - // Import/Export Buttons - GtkWidget *import_btn = gtk_button_new_with_label("Import"); - g_signal_connect(import_btn, "clicked", G_CALLBACK(on_rule_import_clicked), NULL); - - GtkWidget *export_btn = gtk_button_new_with_label("Export"); - g_signal_connect(export_btn, "clicked", G_CALLBACK(on_rule_export_clicked), NULL); - - gtk_box_pack_start(GTK_BOX(header_box), title, FALSE, FALSE, 0); - - // Spacing - GtkWidget *spacer = gtk_label_new(""); - gtk_widget_set_hexpand(spacer, TRUE); - gtk_box_pack_start(GTK_BOX(header_box), spacer, TRUE, TRUE, 0); - - // Buttons (Packed End = Right to Left order on screen) - gtk_box_pack_end(GTK_BOX(header_box), add_btn, FALSE, FALSE, 5); - gtk_box_pack_end(GTK_BOX(header_box), btn_select_all_header, FALSE, FALSE, 5); - gtk_box_pack_end(GTK_BOX(header_box), del_all_btn, FALSE, FALSE, 5); - gtk_box_pack_end(GTK_BOX(header_box), export_btn, FALSE, FALSE, 5); - gtk_box_pack_end(GTK_BOX(header_box), import_btn, FALSE, FALSE, 5); - - gtk_box_pack_start(GTK_BOX(vbox), header_box, FALSE, FALSE, 0); - - // Rules List Area - GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL); - gtk_widget_set_vexpand(scrolled, TRUE); - rules_list_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); - gtk_container_add(GTK_CONTAINER(scrolled), rules_list_box); - gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0); - - refresh_rules_ui(); - - gtk_widget_show_all(dialog); -} +GList *g_rules_list = NULL; static void on_log_traffic_toggled(GtkCheckMenuItem *item, gpointer data) { bool active = gtk_check_menu_item_get_active(item); @@ -1127,150 +28,77 @@ static void on_dns_proxy_toggled(GtkCheckMenuItem *item, gpointer data) { ProxyBridge_SetDnsViaProxy(active); } -static void on_about(GtkWidget *widget, gpointer data) { - GtkWidget *dialog = gtk_dialog_new_with_buttons("About ProxyBridge", - GTK_WINDOW(window), - GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, - "OK", GTK_RESPONSE_OK, - NULL); - gtk_window_set_default_size(GTK_WINDOW(dialog), 400, 300); - - GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - gtk_container_set_border_width(GTK_CONTAINER(content_area), 20); - - char *markup = g_strdup_printf( - "ProxyBridge\n" - "Version %s\n\n" - "Universal proxy client for Linux applications\n\n" - "Author: Sourav Kalal / InterceptSuite\n\n" - "Website: interceptsuite.com\n" - "GitHub: InterceptSuite/ProxyBridge\n\n" - "License: MIT", PROXYBRIDGE_VERSION); - - GtkWidget *label = gtk_label_new(NULL); - gtk_label_set_markup(GTK_LABEL(label), markup); - gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER); - g_free(markup); - - gtk_box_pack_start(GTK_BOX(content_area), label, TRUE, TRUE, 0); - gtk_widget_show_all(dialog); - - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); -} - static void on_create_update_script_and_run() { - // We use execl to replace the current process, so we must stop everything first ProxyBridge_Stop(); - const char *script_url = "https://raw.githubusercontent.com/InterceptSuite/ProxyBridge/refs/heads/master/Linux/deploy.sh"; - - // Secure temp directory char tmp_dir_tpl[] = "/tmp/pb_update_XXXXXX"; char *tmp_dir = mkdtemp(tmp_dir_tpl); - if (!tmp_dir) { - fprintf(stderr, "Failed to create temp directory for update.\n"); - exit(1); - } - + if (!tmp_dir) { fprintf(stderr, "Failed to create temp directory for update.\n"); exit(1); } char script_path[512]; snprintf(script_path, sizeof(script_path), "%s/deploy.sh", tmp_dir); - // Download using fork/exec of curl, safer than system() pid_t pid = fork(); - if (pid == -1) { - fprintf(stderr, "Fork failed.\n"); - exit(1); - } else if (pid == 0) { - // Child - execlp("curl", "curl", "-s", "-o", script_path, script_url, NULL); - _exit(127); - } else { - // Parent - int status; - waitpid(pid, &status, 0); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - fprintf(stderr, "Failed to download update script.\n"); - exit(1); - } - } - - // chmod +x - if (chmod(script_path, S_IRWXU) != 0) { - perror("chmod failed"); - exit(1); - } + if (pid == -1) { fprintf(stderr, "Fork failed.\n"); exit(1); } + else if (pid == 0) { execlp("curl", "curl", "-s", "-o", script_path, script_url, NULL); _exit(127); } + else { int status; waitpid(pid, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { fprintf(stderr, "Failed to download update script.\n"); exit(1); } } - // Replace process with the update script + if (chmod(script_path, S_IRWXU) != 0) { perror("chmod failed"); exit(1); } execl("/bin/bash", "bash", script_path, NULL); - exit(0); // Should not reach here + exit(0); } static void on_check_update(GtkWidget *widget, gpointer data) { const char *url = "https://api.github.com/repos/InterceptSuite/ProxyBridge/releases/latest"; char *cmd = g_strdup_printf("curl -s -H \"User-Agent: ProxyBridge-Linux\" %s", url); - char *standard_output = NULL; char *standard_error = NULL; GError *error = NULL; int exit_status = 0; - // Use GLib spawn instead of popen for safety and correct signal handling - gboolean result = g_spawn_command_line_sync(cmd, - &standard_output, - &standard_error, - &exit_status, - &error); + gboolean result = g_spawn_command_line_sync(cmd, &standard_output, &standard_error, &exit_status, &error); g_free(cmd); - - if (!result) { - show_message(GTK_WINDOW(window), GTK_MESSAGE_ERROR, "Failed to launch update check: %s", error ? error->message : "Unknown error"); - if (error) g_error_free(error); - return; - } - - if (exit_status != 0 || !standard_output || strlen(standard_output) == 0) { - show_message(GTK_WINDOW(window), GTK_MESSAGE_ERROR, "Update check failed (Exit: %d).", exit_status); - g_free(standard_output); - g_free(standard_error); - return; - } + if (!result) { show_message(GTK_WINDOW(window), GTK_MESSAGE_ERROR, "Failed to launch release check: %s", error ? error->message : "Unknown"); if (error) g_error_free(error); return; } + if (exit_status != 0 || !standard_output || strlen(standard_output) == 0) { show_message(GTK_WINDOW(window), GTK_MESSAGE_ERROR, "Update check failed (Exit: %d).", exit_status); g_free(standard_output); g_free(standard_error); return; } - // Copy to buffer for existing logic (simplified) - // Or just use standard_output directly char *tag_name = extract_sub_json_str(standard_output, "tag_name"); - g_free(standard_output); - g_free(standard_error); + g_free(standard_output); g_free(standard_error); - if (!tag_name) { - show_message(GTK_WINDOW(window), GTK_MESSAGE_WARNING, "Could not parse version info.\nResponse might be rate limited."); - return; - } - - // Compare + if (!tag_name) { show_message(GTK_WINDOW(window), GTK_MESSAGE_WARNING, "Could not parse version info."); return; } char *current_tag = g_strdup_printf("v%s", PROXYBRIDGE_VERSION); - if (strcmp(tag_name, current_tag) == 0) { - show_message(GTK_WINDOW(window), GTK_MESSAGE_INFO, "You are using the latest version (%s).", PROXYBRIDGE_VERSION); - } else { - GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_NONE, - "New version %s is available!\nCurrent version: %s\n\nDo you want to update now?", - tag_name, PROXYBRIDGE_VERSION); + if (strcmp(tag_name, current_tag) == 0) { show_message(GTK_WINDOW(window), GTK_MESSAGE_INFO, "You are using the latest version (%s).", PROXYBRIDGE_VERSION); } + else { + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "New version %s is available!\nCurrent: %s\n\nUpdate now?", tag_name, PROXYBRIDGE_VERSION); gtk_dialog_add_button(GTK_DIALOG(dialog), "Download Now", GTK_RESPONSE_ACCEPT); gtk_dialog_add_button(GTK_DIALOG(dialog), "Close", GTK_RESPONSE_CANCEL); - int resp = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); - - if (resp == GTK_RESPONSE_ACCEPT) { - on_create_update_script_and_run(); - } + if (resp == GTK_RESPONSE_ACCEPT) on_create_update_script_and_run(); } - g_free(current_tag); - g_free(tag_name); + g_free(current_tag); g_free(tag_name); +} + +static void on_about(GtkWidget *widget, gpointer data) { + GtkWidget *dialog = gtk_dialog_new_with_buttons("About ProxyBridge", GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, "OK", GTK_RESPONSE_OK, NULL); + gtk_window_set_default_size(GTK_WINDOW(dialog), 400, 300); + GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_set_border_width(GTK_CONTAINER(content_area), 20); + char *markup = g_strdup_printf( + "ProxyBridge\n" + "Version %s\n\n" + "Universal proxy client for Linux applications\n\n" + "Author: Sourav Kalal / InterceptSuite\n\n" + "Website: interceptsuite.com\n" + "GitHub: InterceptSuite/ProxyBridge\n\n" + "License: MIT", PROXYBRIDGE_VERSION); + GtkWidget *label = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(label), markup); + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER); + g_free(markup); + gtk_box_pack_start(GTK_BOX(content_area), label, TRUE, TRUE, 0); + gtk_widget_show_all(dialog); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); } static void signal_handler(int sig) { @@ -1284,35 +112,17 @@ static void on_window_destroy(GtkWidget *widget, gpointer data) { gtk_main_quit(); } -// --- Main Init --- - int main(int argc, char *argv[]) { signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGSEGV, signal_handler); - // Only verify root if not argument --help or similar passed? - // But GTK also takes args. - // Simply check uid. - if (getuid() != 0) { - // Can't show GUI dialog easily without GTK init which might fail if no display. - // Try init, then dialog. - gtk_init(&argc, &argv); - show_message(NULL, GTK_MESSAGE_ERROR, "ProxyBridge must be run as root (sudo)."); - return 1; - } - - // Force GSettings backend to 'memory' to prevent dconf/dbus-launch errors when running as root - // This suppresses "failed to commit changes to dconf" warnings. + if (getuid() != 0) { gtk_init(&argc, &argv); show_message(NULL, GTK_MESSAGE_ERROR, "ProxyBridge must be run as root (sudo)."); return 1; } setenv("GSETTINGS_BACKEND", "memory", 1); - gtk_init(&argc, &argv); - // Apply dark theme preference if available GtkSettings *settings = gtk_settings_get_default(); - if (settings) { - g_object_set(settings, "gtk-application-prefer-dark-theme", TRUE, NULL); - } + if (settings) g_object_set(settings, "gtk-application-prefer-dark-theme", TRUE, NULL); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "ProxyBridge"); @@ -1322,21 +132,19 @@ int main(int argc, char *argv[]) { GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_container_add(GTK_CONTAINER(window), vbox); - // Menu Bar + // Menu GtkWidget *menubar = gtk_menu_bar_new(); - GtkWidget *proxy_menu_item = gtk_menu_item_new_with_label("Proxy"); GtkWidget *proxy_menu = gtk_menu_new(); GtkWidget *config_item = gtk_menu_item_new_with_label("Proxy Settings"); GtkWidget *rules_item = gtk_menu_item_new_with_label("Proxy Rules"); - // New Check Menu Items GtkWidget *log_check_item = gtk_check_menu_item_new_with_label("Enable Traffic Logging"); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(log_check_item), TRUE); // Default + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(log_check_item), TRUE); g_signal_connect(log_check_item, "toggled", G_CALLBACK(on_log_traffic_toggled), NULL); GtkWidget *dns_check_item = gtk_check_menu_item_new_with_label("DNS via Proxy"); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(dns_check_item), TRUE); // Default + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(dns_check_item), TRUE); g_signal_connect(dns_check_item, "toggled", G_CALLBACK(on_dns_proxy_toggled), NULL); GtkWidget *exit_item = gtk_menu_item_new_with_label("Exit"); @@ -1356,16 +164,12 @@ int main(int argc, char *argv[]) { GtkWidget *about_menu_item = gtk_menu_item_new_with_label("About"); GtkWidget *about_menu = gtk_menu_new(); - GtkWidget *about_child_item = gtk_menu_item_new_with_label("About"); g_signal_connect(about_child_item, "activate", G_CALLBACK(on_about), NULL); - GtkWidget *update_item = gtk_menu_item_new_with_label("Check for Updates"); g_signal_connect(update_item, "activate", G_CALLBACK(on_check_update), NULL); - gtk_menu_shell_append(GTK_MENU_SHELL(about_menu), about_child_item); gtk_menu_shell_append(GTK_MENU_SHELL(about_menu), update_item); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(about_menu_item), about_menu); gtk_menu_shell_append(GTK_MENU_SHELL(menubar), proxy_menu_item); @@ -1376,69 +180,48 @@ int main(int argc, char *argv[]) { GtkWidget *notebook = gtk_notebook_new(); gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); - // --- Tab 1: Connections --- + // Tab 1: Connections GtkWidget *conn_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); gtk_container_set_border_width(GTK_CONTAINER(conn_vbox), 5); - - // Toolbar (Search + Clear) GtkWidget *conn_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); - GtkWidget *conn_search = gtk_search_entry_new(); gtk_entry_set_placeholder_text(GTK_ENTRY(conn_search), "Search connections..."); g_signal_connect(conn_search, "search-changed", G_CALLBACK(on_search_conn_changed), NULL); - GtkWidget *conn_clear_btn = gtk_button_new_with_label("Clear Logs"); g_signal_connect(conn_clear_btn, "clicked", G_CALLBACK(on_clear_conn_clicked), NULL); - gtk_box_pack_start(GTK_BOX(conn_toolbar), conn_search, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(conn_toolbar), conn_clear_btn, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(conn_vbox), conn_toolbar, FALSE, FALSE, 0); - - // List View (Now Text View) conn_view = GTK_TEXT_VIEW(gtk_text_view_new()); gtk_text_view_set_editable(conn_view, FALSE); gtk_text_view_set_cursor_visible(conn_view, FALSE); conn_buffer = gtk_text_view_get_buffer(conn_view); - - // Create tag for filtering gtk_text_buffer_create_tag(conn_buffer, "hidden", "invisible", TRUE, NULL); - GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); gtk_container_add(GTK_CONTAINER(scrolled_window), GTK_WIDGET(conn_view)); gtk_box_pack_start(GTK_BOX(conn_vbox), scrolled_window, TRUE, TRUE, 0); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), conn_vbox, gtk_label_new("Connections")); - // --- Tab 2: Activity Logs --- + // Tab 2: Logs GtkWidget *log_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); gtk_container_set_border_width(GTK_CONTAINER(log_vbox), 5); - - // Toolbar (Search + Clear) GtkWidget *log_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); - GtkWidget *log_search = gtk_search_entry_new(); gtk_entry_set_placeholder_text(GTK_ENTRY(log_search), "Search logs..."); g_signal_connect(log_search, "search-changed", G_CALLBACK(on_search_log_changed), NULL); - GtkWidget *log_clear_btn = gtk_button_new_with_label("Clear Logs"); g_signal_connect(log_clear_btn, "clicked", G_CALLBACK(on_clear_log_clicked), NULL); - gtk_box_pack_start(GTK_BOX(log_toolbar), log_search, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(log_toolbar), log_clear_btn, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(log_vbox), log_toolbar, FALSE, FALSE, 0); - log_view = GTK_TEXT_VIEW(gtk_text_view_new()); gtk_text_view_set_editable(log_view, FALSE); gtk_text_view_set_cursor_visible(log_view, FALSE); log_buffer = gtk_text_view_get_buffer(log_view); - - // Create tag for filtering gtk_text_buffer_create_tag(log_buffer, "hidden", "invisible", TRUE, NULL); - GtkWidget *log_scroll = gtk_scrolled_window_new(NULL, NULL); gtk_container_add(GTK_CONTAINER(log_scroll), GTK_WIDGET(log_view)); gtk_box_pack_start(GTK_BOX(log_vbox), log_scroll, TRUE, TRUE, 0); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), log_vbox, gtk_label_new("Activity Logs")); // Status Bar @@ -1451,7 +234,6 @@ int main(int argc, char *argv[]) { ProxyBridge_SetConnectionCallback(lib_connection_callback); ProxyBridge_SetTrafficLoggingEnabled(true); - // Start Proxy Engine if (ProxyBridge_Start()) { gtk_statusbar_push(GTK_STATUSBAR(status_bar), status_context_id, "ProxyBridge Service Started. Please configure proxy settings."); } else { From c1c99d94b927aa376702ca6ef82fd84e346d7e4b Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 23:16:04 +0530 Subject: [PATCH 23/58] updated GUI --- Linux/gui/gui.h | 14 +++----------- Linux/gui/gui_logs.c | 10 +++++----- Linux/gui/gui_rules.c | 19 +++++++++++-------- Linux/gui/gui_settings.c | 32 ++++++++++++++++---------------- Linux/gui/gui_utils.c | 16 ++++++++-------- Linux/gui/main.c | 16 ++++++++-------- 6 files changed, 51 insertions(+), 56 deletions(-) diff --git a/Linux/gui/gui.h b/Linux/gui/gui.h index d0e57de..777f2bf 100644 --- a/Linux/gui/gui.h +++ b/Linux/gui/gui.h @@ -13,9 +13,6 @@ #include #include #include "ProxyBridge.h" - -// --- Data Structures --- - typedef struct { char *process_name; uint32_t pid; @@ -53,7 +50,6 @@ typedef struct { bool selected; } RuleData; -// --- Thread Communication --- struct TestRunnerData { char *host; uint16_t port; @@ -66,7 +62,6 @@ typedef struct { GtkWidget *btn; } TestResultData; -// --- Globals (External) --- extern GtkWidget *window; extern GtkTextBuffer *conn_buffer; extern GtkTextBuffer *log_buffer; @@ -81,9 +76,6 @@ extern char g_proxy_pass[256]; extern GList *g_rules_list; -// --- Function Prototypes --- - -// Utils long safe_strtol(const char *nptr); void show_message(GtkWindow *parent, GtkMessageType type, const char *format, ...); void trim_buffer_lines(GtkTextBuffer *buffer, int max_lines); @@ -92,10 +84,10 @@ char *escape_json_string(const char *src); char *extract_sub_json_str(const char *json, const char *key); bool extract_sub_json_bool(const char *json, const char *key); -// Settings +// settings void on_proxy_configure(GtkWidget *widget, gpointer data); -// Rules +// rules section void on_proxy_rules_clicked(GtkWidget *widget, gpointer data); // Logs @@ -106,4 +98,4 @@ void on_search_log_changed(GtkSearchEntry *entry, gpointer user_data); void on_clear_conn_clicked(GtkButton *button, gpointer user_data); void on_clear_log_clicked(GtkButton *button, gpointer user_data); -#endif // PROXYBRIDGE_GUI_H +#endif diff --git a/Linux/gui/gui_logs.c b/Linux/gui/gui_logs.c index 8803348..3410269 100644 --- a/Linux/gui/gui_logs.c +++ b/Linux/gui/gui_logs.c @@ -1,6 +1,6 @@ #include "gui.h" -// Filter Function for TextView +// filter the text view based on user imput static void filter_text_view(GtkTextBuffer *buffer, const char *text) { if (!buffer) return; @@ -18,7 +18,7 @@ static void filter_text_view(GtkTextBuffer *buffer, const char *text) { char *line_text = gtk_text_buffer_get_text(buffer, &line_start, &line_end, FALSE); - // Case-insensitive search + // case insensitive search char *lower_line = g_utf8_strdown(line_text, -1); char *lower_search = g_utf8_strdown(text, -1); @@ -64,7 +64,7 @@ static void free_connection_data(ConnectionData *data) { } } -// Thread-safe idle callback for Log +// update logs safely from main thread static gboolean update_log_gui(gpointer user_data) { LogData *data = (LogData *)user_data; if (!data) return FALSE; @@ -83,10 +83,10 @@ static gboolean update_log_gui(gpointer user_data) { free(data->message); free(data); - return FALSE; // Remove source + return FALSE; // done } -// Thread-safe idle callback for Connection +// update connection info safely static gboolean update_connection_gui_append(gpointer user_data) { ConnectionData *data = (ConnectionData *)user_data; if (!data) return FALSE; diff --git a/Linux/gui/gui_rules.c b/Linux/gui/gui_rules.c index 52b1718..2beca0f 100644 --- a/Linux/gui/gui_rules.c +++ b/Linux/gui/gui_rules.c @@ -3,7 +3,7 @@ static GtkWidget *rules_list_box = NULL; static GtkWidget *btn_select_all_header = NULL; -// Forward declaration +// forward decl static void refresh_rules_ui(); static void free_rule_data(RuleData *rule) { @@ -33,7 +33,7 @@ static void on_rule_toggle(GtkToggleButton *btn, gpointer data) { static void on_save_rule(GtkWidget *widget, gpointer data) { GtkWidget **widgets = (GtkWidget **)data; GtkWidget *dialog = widgets[0]; - RuleData *edit_rule = (RuleData *)widgets[6]; // If not null, we are editing + RuleData *edit_rule = (RuleData *)widgets[6]; // existing rule if present const char *proc = gtk_entry_get_text(GTK_ENTRY(widgets[1])); const char *hosts = gtk_entry_get_text(GTK_ENTRY(widgets[2])); @@ -41,16 +41,19 @@ static void on_save_rule(GtkWidget *widget, gpointer data) { RuleProtocol proto = gtk_combo_box_get_active(GTK_COMBO_BOX(widgets[4])); RuleAction action = gtk_combo_box_get_active(GTK_COMBO_BOX(widgets[5])); - if (strlen(proc) == 0) return; // Error + if (strlen(proc) == 0) return; // need process name if (edit_rule) { + // update backend ProxyBridge_EditRule(edit_rule->id, proc, hosts, ports, proto, action); + // update local free(edit_rule->process_name); edit_rule->process_name = strdup(proc); free(edit_rule->target_hosts); edit_rule->target_hosts = strdup(hosts); free(edit_rule->target_ports); edit_rule->target_ports = strdup(ports); edit_rule->protocol = proto; edit_rule->action = action; } else { + // new rule uint32_t new_id = ProxyBridge_AddRule(proc, hosts, ports, proto, action); RuleData *new_rule = malloc(sizeof(RuleData)); new_rule->id = new_id; @@ -99,7 +102,7 @@ static void open_rule_dialog(RuleData *rule) { gtk_grid_set_column_spacing(GTK_GRID(grid), 10); gtk_container_set_border_width(GTK_CONTAINER(grid), 15); - // Process + // process input GtkWidget *proc_entry = gtk_entry_new(); GtkWidget *browse_btn = gtk_button_new_with_label("Browse..."); g_signal_connect(browse_btn, "clicked", G_CALLBACK(on_browse_clicked), proc_entry); @@ -110,13 +113,13 @@ static void open_rule_dialog(RuleData *rule) { GtkWidget *host_entry = gtk_entry_new(); GtkWidget *port_entry = gtk_entry_new(); - // Protocol + // protocol list GtkWidget *proto_combo = gtk_combo_box_text_new(); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "TCP"); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "UDP"); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "BOTH"); - // Action + // action list GtkWidget *action_combo = gtk_combo_box_text_new(); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "PROXY"); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "DIRECT"); @@ -131,8 +134,8 @@ static void open_rule_dialog(RuleData *rule) { } else { gtk_entry_set_text(GTK_ENTRY(port_entry), "*"); gtk_entry_set_text(GTK_ENTRY(host_entry), "*"); - gtk_combo_box_set_active(GTK_COMBO_BOX(proto_combo), 2); // BOTH - gtk_combo_box_set_active(GTK_COMBO_BOX(action_combo), 0); // PROXY + gtk_combo_box_set_active(GTK_COMBO_BOX(proto_combo), 2); // BOTH default + gtk_combo_box_set_active(GTK_COMBO_BOX(action_combo), 0); // PROXY default } int row = 0; diff --git a/Linux/gui/gui_settings.c b/Linux/gui/gui_settings.c index 9a13d48..2cad586 100644 --- a/Linux/gui/gui_settings.c +++ b/Linux/gui/gui_settings.c @@ -1,6 +1,6 @@ #include "gui.h" -// --- Settings Dialog --- +// settings view logic static gboolean on_test_done(gpointer user_data) { TestResultData *data = (TestResultData *)user_data; @@ -18,7 +18,7 @@ static gpointer run_test_thread(gpointer user_data) { char *buffer = malloc(4096); memset(buffer, 0, 4096); - // Check callback if needed, but TestConnection usually returns result in buffer + // run the test ProxyBridge_TestConnection(req->host, req->port, buffer, 4096); TestResultData *res = malloc(sizeof(TestResultData)); @@ -36,7 +36,7 @@ static gpointer run_test_thread(gpointer user_data) { static void on_start_test_clicked(GtkWidget *widget, gpointer data) { ConfigInfo *info = (ConfigInfo *)data; - // 1. Validate Proxy Config + // validate inputs const char *ip_text = gtk_entry_get_text(GTK_ENTRY(info->ip_entry)); const char *port_text = gtk_entry_get_text(GTK_ENTRY(info->port_entry)); @@ -45,7 +45,7 @@ static void on_start_test_clicked(GtkWidget *widget, gpointer data) { return; } - // 2. Set Proxy Config + // save config ProxyType type = (gtk_combo_box_get_active(GTK_COMBO_BOX(info->type_combo)) == 0) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; int port = (int)safe_strtol(port_text); const char *user = gtk_entry_get_text(GTK_ENTRY(info->user_entry)); @@ -53,7 +53,7 @@ static void on_start_test_clicked(GtkWidget *widget, gpointer data) { ProxyBridge_SetProxyConfig(type, ip_text, port, user, pass); - // 3. Get Test Target + // get target const char *t_host = gtk_entry_get_text(GTK_ENTRY(info->test_host)); const char *t_port_s = gtk_entry_get_text(GTK_ENTRY(info->test_port)); @@ -61,11 +61,11 @@ static void on_start_test_clicked(GtkWidget *widget, gpointer data) { int t_port = (int)safe_strtol(t_port_s); if (t_port <= 0) t_port = 80; - // 4. Update UI + // update gui gtk_text_buffer_set_text(info->output_buffer, "Testing connection... Please wait...", -1); gtk_widget_set_sensitive(info->test_btn, FALSE); - // 5. Run Thread + // start thread struct TestRunnerData *req = malloc(sizeof(struct TestRunnerData)); req->host = strdup(t_host); req->port = t_port; @@ -87,7 +87,7 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL); - // Increase width for log + // make it wider gtk_window_set_default_size(GTK_WINDOW(info.dialog), 600, 500); content_area = gtk_dialog_get_content_area(GTK_DIALOG(info.dialog)); @@ -97,22 +97,22 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { gtk_container_set_border_width(GTK_CONTAINER(grid), 10); gtk_box_pack_start(GTK_BOX(content_area), grid, TRUE, TRUE, 0); - // Type + // type selection gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Type:"), 0, 0, 1, 1); info.type_combo = gtk_combo_box_text_new(); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(info.type_combo), "HTTP"); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(info.type_combo), "SOCKS5"); gtk_combo_box_set_active(GTK_COMBO_BOX(info.type_combo), g_proxy_type == PROXY_TYPE_HTTP ? 0 : 1); - gtk_grid_attach(GTK_GRID(grid), info.type_combo, 1, 0, 3, 1); // Span 3 + gtk_grid_attach(GTK_GRID(grid), info.type_combo, 1, 0, 3, 1); - // IP + // ip address gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Host:"), 0, 1, 1, 1); info.ip_entry = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(info.ip_entry), g_proxy_ip); gtk_widget_set_hexpand(info.ip_entry, TRUE); gtk_grid_attach(GTK_GRID(grid), info.ip_entry, 1, 1, 3, 1); - // Port + // port number gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Port:"), 0, 2, 1, 1); info.port_entry = gtk_entry_new(); if (g_proxy_port != 0) { @@ -122,20 +122,20 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { } gtk_grid_attach(GTK_GRID(grid), info.port_entry, 1, 2, 3, 1); - // User + // username gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Username:"), 0, 3, 1, 1); info.user_entry = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(info.user_entry), g_proxy_user); gtk_grid_attach(GTK_GRID(grid), info.user_entry, 1, 3, 3, 1); - // Pass + // password gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Password:"), 0, 4, 1, 1); info.pass_entry = gtk_entry_new(); gtk_entry_set_visibility(GTK_ENTRY(info.pass_entry), FALSE); gtk_entry_set_text(GTK_ENTRY(info.pass_entry), g_proxy_pass); gtk_grid_attach(GTK_GRID(grid), info.pass_entry, 1, 4, 3, 1); - // --- Test Section --- + // test connection section gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 5, 4, 1); GtkWidget *test_label = gtk_label_new("Test Connection"); @@ -143,7 +143,7 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { gtk_widget_set_halign(test_label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID(grid), test_label, 0, 6, 4, 1); - // Target Host + // target host gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target:"), 0, 7, 1, 1); info.test_host = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(info.test_host), "google.com"); diff --git a/Linux/gui/gui_utils.c b/Linux/gui/gui_utils.c index a1f6931..59e0821 100644 --- a/Linux/gui/gui_utils.c +++ b/Linux/gui/gui_utils.c @@ -1,15 +1,15 @@ #include "gui.h" -// Helper for safe integer conversion +// safer way to turn string to int long safe_strtol(const char *nptr) { - if (!nptr) return 0; // Handle NULL + if (!nptr) return 0; // null check char *endptr; long val = strtol(nptr, &endptr, 10); - if (endptr == nptr) return 0; // No digits found + if (endptr == nptr) return 0; // bad input return val; } -// Helper for quick message dialogs +// show simple popup void show_message(GtkWindow *parent, GtkMessageType type, const char *format, ...) { va_list args; va_start(args, format); @@ -26,7 +26,7 @@ void show_message(GtkWindow *parent, GtkMessageType type, const char *format, .. g_free(msg); } -// Helper to limit buffer lines +// limit log size so it doesnt eat ram void trim_buffer_lines(GtkTextBuffer *buffer, int max_lines) { if (gtk_text_buffer_get_line_count(buffer) > max_lines) { GtkTextIter start, next; @@ -47,7 +47,7 @@ char* get_current_time_str() { return buffer; } -// --- JSON Helpers --- +// json stuff char *escape_json_string(const char *src) { if (!src) return strdup(""); GString *str = g_string_new(""); @@ -60,7 +60,7 @@ char *escape_json_string(const char *src) { return g_string_free(str, FALSE); } -// Very basic JSON parser for valid input +// basic parser, good enough for valid inputs char *extract_sub_json_str(const char *json, const char *key) { char search_key[256]; snprintf(search_key, sizeof(search_key), "\"%s\"", key); @@ -83,7 +83,7 @@ bool extract_sub_json_bool(const char *json, const char *key) { if (!k) return false; char *colon = strchr(k, ':'); if (!colon) return false; - // Skip spaces + // skip whitespace char *v = colon + 1; while(*v == ' ' || *v == '\t') v++; if (strncmp(v, "true", 4) == 0) return true; diff --git a/Linux/gui/main.c b/Linux/gui/main.c index aaa3294..da7efb9 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -1,6 +1,6 @@ #include "gui.h" -// --- Global UI Widgets --- +// ui widgets GtkWidget *window; GtkTextView *conn_view; GtkTextBuffer *conn_buffer; @@ -9,7 +9,7 @@ GtkTextBuffer *log_buffer; GtkWidget *status_bar; guint status_context_id; -// --- Config Globals (Defaults) --- +// default config char g_proxy_ip[256] = ""; uint16_t g_proxy_port = 0; ProxyType g_proxy_type = PROXY_TYPE_SOCKS5; @@ -132,7 +132,7 @@ int main(int argc, char *argv[]) { GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_container_add(GTK_CONTAINER(window), vbox); - // Menu + // menu setup GtkWidget *menubar = gtk_menu_bar_new(); GtkWidget *proxy_menu_item = gtk_menu_item_new_with_label("Proxy"); GtkWidget *proxy_menu = gtk_menu_new(); @@ -176,11 +176,11 @@ int main(int argc, char *argv[]) { gtk_menu_shell_append(GTK_MENU_SHELL(menubar), about_menu_item); gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); - // Tabs + // tabs GtkWidget *notebook = gtk_notebook_new(); gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); - // Tab 1: Connections + // active connections tab GtkWidget *conn_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); gtk_container_set_border_width(GTK_CONTAINER(conn_vbox), 5); GtkWidget *conn_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); @@ -202,7 +202,7 @@ int main(int argc, char *argv[]) { gtk_box_pack_start(GTK_BOX(conn_vbox), scrolled_window, TRUE, TRUE, 0); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), conn_vbox, gtk_label_new("Connections")); - // Tab 2: Logs + // logs tab GtkWidget *log_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); gtk_container_set_border_width(GTK_CONTAINER(log_vbox), 5); GtkWidget *log_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); @@ -224,12 +224,12 @@ int main(int argc, char *argv[]) { gtk_box_pack_start(GTK_BOX(log_vbox), log_scroll, TRUE, TRUE, 0); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), log_vbox, gtk_label_new("Activity Logs")); - // Status Bar + // status bar status_bar = gtk_statusbar_new(); status_context_id = gtk_statusbar_get_context_id(GTK_STATUSBAR(status_bar), "Status"); gtk_box_pack_start(GTK_BOX(vbox), status_bar, FALSE, FALSE, 0); - // Initial Config + // start it up ProxyBridge_SetLogCallback(lib_log_callback); ProxyBridge_SetConnectionCallback(lib_connection_callback); ProxyBridge_SetTrafficLoggingEnabled(true); From c34f266556fdc62fe3117afb52eac0e940b27985 Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sat, 7 Feb 2026 23:30:17 +0530 Subject: [PATCH 24/58] added local storage support --- Linux/gui/gui.h | 6 +++ Linux/gui/gui_config.c | 109 +++++++++++++++++++++++++++++++++++++++ Linux/gui/gui_rules.c | 3 ++ Linux/gui/gui_settings.c | 2 + Linux/gui/main.c | 29 +++++++++-- 5 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 Linux/gui/gui_config.c diff --git a/Linux/gui/gui.h b/Linux/gui/gui.h index 777f2bf..035c1ee 100644 --- a/Linux/gui/gui.h +++ b/Linux/gui/gui.h @@ -75,6 +75,8 @@ extern char g_proxy_user[256]; extern char g_proxy_pass[256]; extern GList *g_rules_list; +extern bool g_chk_logging; +extern bool g_chk_dns; long safe_strtol(const char *nptr); void show_message(GtkWindow *parent, GtkMessageType type, const char *format, ...); @@ -84,6 +86,10 @@ char *escape_json_string(const char *src); char *extract_sub_json_str(const char *json, const char *key); bool extract_sub_json_bool(const char *json, const char *key); +// config storage +void save_config(); +void load_config(); + // settings void on_proxy_configure(GtkWidget *widget, gpointer data); diff --git a/Linux/gui/gui_config.c b/Linux/gui/gui_config.c new file mode 100644 index 0000000..ee184dc --- /dev/null +++ b/Linux/gui/gui_config.c @@ -0,0 +1,109 @@ +#include "gui.h" + +#define CONFIG_DIR "/etc/proxybridge" +#define CONFIG_PATH "/etc/proxybridge/config.ini" + +// save settings to file +void save_config() { + struct stat st = {0}; + if (stat(CONFIG_DIR, &st) == -1) { + if (mkdir(CONFIG_DIR, 0755) != 0) { + perror("failed to create config dir"); + return; + } + } + + FILE *f = fopen(CONFIG_PATH, "w"); + if (!f) { + printf("failed to save config to %s\n", CONFIG_PATH); + return; + } + + // settings section + fprintf(f, "[SETTINGS]\n"); + fprintf(f, "ip=%s\n", g_proxy_ip); + fprintf(f, "port=%d\n", g_proxy_port); + fprintf(f, "type=%d\n", g_proxy_type); + fprintf(f, "user=%s\n", g_proxy_user); + fprintf(f, "pass=%s\n", g_proxy_pass); + fprintf(f, "logging=%d\n", g_chk_logging); + fprintf(f, "dns=%d\n", g_chk_dns); + + // rules section + fprintf(f, "[RULES]\n"); + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *rule = (RuleData *)l->data; + // format: id|protocol|action|enabled|procname|hosts|ports + fprintf(f, "%u|%d|%d|%d|%s|%s|%s\n", + rule->id, + rule->protocol, + rule->action, + rule->enabled, + rule->process_name ? rule->process_name : "", + rule->target_hosts ? rule->target_hosts : "", + rule->target_ports ? rule->target_ports : "" + ); + } + + fclose(f); +} + +// load settings from file +void load_config() { + FILE *f = fopen(CONFIG_PATH, "r"); + if (!f) return; + + char line[2048]; + int section = 0; // 0=none, 1=settings, 2=rules + + while (fgets(line, sizeof(line), f)) { + // trim newline + line[strcspn(line, "\r\n")] = 0; + + if (strlen(line) == 0 || line[0] == '#') continue; + + if (strcmp(line, "[SETTINGS]") == 0) { section = 1; continue; } + if (strcmp(line, "[RULES]") == 0) { section = 2; continue; } + + if (section == 1) { + char *val = strchr(line, '='); + if (!val) continue; + *val = 0; val++; + + if (strcmp(line, "ip") == 0) strncpy(g_proxy_ip, val, sizeof(g_proxy_ip) - 1); + else if (strcmp(line, "port") == 0) g_proxy_port = atoi(val); + else if (strcmp(line, "type") == 0) g_proxy_type = atoi(val); + else if (strcmp(line, "user") == 0) strncpy(g_proxy_user, val, sizeof(g_proxy_user) - 1); + else if (strcmp(line, "pass") == 0) strncpy(g_proxy_pass, val, sizeof(g_proxy_pass) - 1); + else if (strcmp(line, "logging") == 0) g_chk_logging = atoi(val); + else if (strcmp(line, "dns") == 0) g_chk_dns = atoi(val); + } + else if (section == 2) { + // parse rule line + RuleData *rule = g_malloc0(sizeof(RuleData)); + char *p = line; + char *token; + int idx = 0; + + while ((token = strsep(&p, "|")) != NULL) { + switch(idx) { + case 0: rule->id = atoi(token); break; + case 1: rule->protocol = atoi(token); break; + case 2: rule->action = atoi(token); break; + case 3: rule->enabled = atoi(token); break; + case 4: rule->process_name = g_strdup(token); break; + case 5: rule->target_hosts = g_strdup(token); break; + case 6: rule->target_ports = g_strdup(token); break; + } + idx++; + } + + if (idx >= 4) { + g_rules_list = g_list_append(g_rules_list, rule); + } else { + g_free(rule); + } + } + } + fclose(f); +} \ No newline at end of file diff --git a/Linux/gui/gui_rules.c b/Linux/gui/gui_rules.c index 2beca0f..9a8fde0 100644 --- a/Linux/gui/gui_rules.c +++ b/Linux/gui/gui_rules.c @@ -20,6 +20,7 @@ static void on_rule_delete(GtkWidget *widget, gpointer data) { ProxyBridge_DeleteRule(rule->id); g_rules_list = g_list_remove(g_rules_list, rule); free_rule_data(rule); + save_config(); refresh_rules_ui(); } @@ -28,6 +29,7 @@ static void on_rule_toggle(GtkToggleButton *btn, gpointer data) { rule->enabled = gtk_toggle_button_get_active(btn); if (rule->enabled) ProxyBridge_EnableRule(rule->id); else ProxyBridge_DisableRule(rule->id); + save_config(); } static void on_save_rule(GtkWidget *widget, gpointer data) { @@ -66,6 +68,7 @@ static void on_save_rule(GtkWidget *widget, gpointer data) { new_rule->selected = false; g_rules_list = g_list_append(g_rules_list, new_rule); } + save_config(); refresh_rules_ui(); gtk_widget_destroy(dialog); free(widgets); diff --git a/Linux/gui/gui_settings.c b/Linux/gui/gui_settings.c index 2cad586..c034cf1 100644 --- a/Linux/gui/gui_settings.c +++ b/Linux/gui/gui_settings.c @@ -209,6 +209,8 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { ProxyBridge_SetProxyConfig(g_proxy_type, g_proxy_ip, g_proxy_port, g_proxy_user, g_proxy_pass); + save_config(); + char status_msg[512]; snprintf(status_msg, sizeof(status_msg), "Configuration updated: %s:%d", g_proxy_ip, g_proxy_port); gtk_statusbar_push(GTK_STATUSBAR(status_bar), status_context_id, status_msg); diff --git a/Linux/gui/main.c b/Linux/gui/main.c index da7efb9..025cf65 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -17,15 +17,21 @@ char g_proxy_user[256] = ""; char g_proxy_pass[256] = ""; GList *g_rules_list = NULL; +bool g_chk_logging = true; +bool g_chk_dns = true; static void on_log_traffic_toggled(GtkCheckMenuItem *item, gpointer data) { bool active = gtk_check_menu_item_get_active(item); ProxyBridge_SetTrafficLoggingEnabled(active); + g_chk_logging = active; + save_config(); } static void on_dns_proxy_toggled(GtkCheckMenuItem *item, gpointer data) { bool active = gtk_check_menu_item_get_active(item); ProxyBridge_SetDnsViaProxy(active); + g_chk_dns = active; + save_config(); } static void on_create_update_script_and_run() { @@ -119,6 +125,10 @@ int main(int argc, char *argv[]) { if (getuid() != 0) { gtk_init(&argc, &argv); show_message(NULL, GTK_MESSAGE_ERROR, "ProxyBridge must be run as root (sudo)."); return 1; } setenv("GSETTINGS_BACKEND", "memory", 1); + + // load saved config + load_config(); + gtk_init(&argc, &argv); GtkSettings *settings = gtk_settings_get_default(); @@ -140,11 +150,11 @@ int main(int argc, char *argv[]) { GtkWidget *rules_item = gtk_menu_item_new_with_label("Proxy Rules"); GtkWidget *log_check_item = gtk_check_menu_item_new_with_label("Enable Traffic Logging"); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(log_check_item), TRUE); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(log_check_item), g_chk_logging); g_signal_connect(log_check_item, "toggled", G_CALLBACK(on_log_traffic_toggled), NULL); GtkWidget *dns_check_item = gtk_check_menu_item_new_with_label("DNS via Proxy"); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(dns_check_item), TRUE); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(dns_check_item), g_chk_dns); g_signal_connect(dns_check_item, "toggled", G_CALLBACK(on_dns_proxy_toggled), NULL); GtkWidget *exit_item = gtk_menu_item_new_with_label("Exit"); @@ -232,10 +242,21 @@ int main(int argc, char *argv[]) { // start it up ProxyBridge_SetLogCallback(lib_log_callback); ProxyBridge_SetConnectionCallback(lib_connection_callback); - ProxyBridge_SetTrafficLoggingEnabled(true); + ProxyBridge_SetTrafficLoggingEnabled(g_chk_logging); + ProxyBridge_SetDnsViaProxy(g_chk_dns); if (ProxyBridge_Start()) { - gtk_statusbar_push(GTK_STATUSBAR(status_bar), status_context_id, "ProxyBridge Service Started. Please configure proxy settings."); + // apply config + ProxyBridge_SetProxyConfig(g_proxy_type, g_proxy_ip, g_proxy_port, g_proxy_user, g_proxy_pass); + + // restore rules + for (GList *l = g_rules_list; l != NULL; l = l->next) { + RuleData *r = (RuleData *)l->data; + r->id = ProxyBridge_AddRule(r->process_name, r->target_hosts, r->target_ports, r->protocol, r->action); + if (!r->enabled) ProxyBridge_DisableRule(r->id); + } + + gtk_statusbar_push(GTK_STATUSBAR(status_bar), status_context_id, "ProxyBridge Service Started."); } else { gtk_statusbar_push(GTK_STATUSBAR(status_bar), status_context_id, "Failed to start ProxyBridge engine."); } From 31c3559e9d3837db0caf0184d834819b64eb87da Mon Sep 17 00:00:00 2001 From: anof-cyber Date: Sun, 8 Feb 2026 02:32:06 +0530 Subject: [PATCH 25/58] updated setup --- Linux/{deploy.sh => setup.sh} | 40 +++++++++++++++++++++++------------ Linux/src/ProxyBridge.h | 2 +- 2 files changed, 28 insertions(+), 14 deletions(-) rename Linux/{deploy.sh => setup.sh} (87%) diff --git a/Linux/deploy.sh b/Linux/setup.sh similarity index 87% rename from Linux/deploy.sh rename to Linux/setup.sh index 2844358..a365a16 100644 --- a/Linux/deploy.sh +++ b/Linux/setup.sh @@ -6,14 +6,14 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" echo "" echo "===================================" -echo "ProxyBridge Deployment Script" +echo "ProxyBridge Setup Script" echo "===================================" echo "" # Check if running as root if [ "$EUID" -ne 0 ]; then echo "ERROR: This script must be run as root" - echo "Please run: sudo ./deploy.sh" + echo "Please run: sudo ./setup.sh" exit 1 fi @@ -53,35 +53,36 @@ install_dependencies() { ubuntu|debian|linuxmint|pop|elementary|zorin|kali|raspbian|mx|antix|deepin|lmde) echo "Using apt package manager..." apt-get update -qq - apt-get install -y libnetfilter-queue1 libnfnetlink0 iptables + # libgtk-3-0 is for runtime, libnetfilter-queue1 for functionality + apt-get install -y libnetfilter-queue1 libnfnetlink0 iptables libgtk-3-0 ;; fedora) echo "Using dnf package manager..." - dnf install -y libnetfilter_queue libnfnetlink iptables + dnf install -y libnetfilter_queue libnfnetlink iptables gtk3 ;; rhel|centos|rocky|almalinux) echo "Using yum package manager..." - yum install -y libnetfilter_queue libnfnetlink iptables + yum install -y libnetfilter_queue libnfnetlink iptables gtk3 ;; arch|manjaro|endeavouros|garuda) echo "Using pacman package manager..." - pacman -Sy --noconfirm libnetfilter_queue libnfnetlink iptables + pacman -Sy --noconfirm libnetfilter_queue libnfnetlink iptables gtk3 ;; opensuse*|sles) echo "Using zypper package manager..." - zypper install -y libnetfilter_queue1 libnfnetlink0 iptables + zypper install -y libnetfilter_queue1 libnfnetlink0 iptables gtk3 ;; void) echo "Using xbps package manager..." - xbps-install -Sy libnetfilter_queue libnfnetlink iptables + xbps-install -Sy libnetfilter_queue libnfnetlink iptables gtk3 ;; *) echo "WARNING: Unknown distribution '$DISTRO' (family: '$DISTRO_LIKE')" echo "" echo "Please manually install the following packages:" - echo " Debian/Ubuntu: sudo apt install libnetfilter-queue1 libnfnetlink0 iptables" - echo " Fedora: sudo dnf install libnetfilter_queue libnfnetlink iptables" - echo " Arch: sudo pacman -S libnetfilter_queue libnfnetlink iptables" + echo " Debian/Ubuntu: sudo apt install libnetfilter-queue1 libnfnetlink0 iptables libgtk-3-0" + echo " Fedora: sudo dnf install libnetfilter_queue libnfnetlink iptables gtk3" + echo " Arch: sudo pacman -S libnetfilter_queue libnfnetlink iptables gtk3" echo "" read -p "Continue anyway? (y/n) " -n 1 -r echo @@ -115,6 +116,10 @@ check_files() { exit 1 fi + if [ ! -f "$SCRIPT_DIR/ProxyBridgeGUI" ]; then + echo "WARNING: ProxyBridgeGUI binary not found in $SCRIPT_DIR - GUI will not be installed" + fi + echo "All files present" } @@ -124,8 +129,8 @@ install_files() { echo "Installing ProxyBridge..." # Create directories if they don't exist - mkdir -p "$LIB_PATH" - mkdir -p /usr/local/bin + mkdir -p "$LIB_PATH" /usr/local/bin /etc/proxybridge + chmod 755 /etc/proxybridge # Copy library echo "Installing libproxybridge.so to $LIB_PATH..." @@ -136,6 +141,12 @@ install_files() { echo "Installing ProxyBridge to /usr/local/bin..." cp "$SCRIPT_DIR/ProxyBridge" /usr/local/bin/ chmod 755 /usr/local/bin/ProxyBridge + + if [ -f "$SCRIPT_DIR/ProxyBridgeGUI" ]; then + echo "Installing ProxyBridgeGUI to /usr/local/bin..." + cp "$SCRIPT_DIR/ProxyBridgeGUI" /usr/local/bin/ + chmod 755 /usr/local/bin/ProxyBridgeGUI + fi echo "Files installed" } @@ -213,6 +224,9 @@ main() { echo "" echo "You can now run ProxyBridge from anywhere:" echo " sudo ProxyBridge --help" + if [ -f /usr/local/bin/ProxyBridgeGUI ]; then + echo " sudo ProxyBridgeGUI (Graphical Interface)" + fi echo " sudo ProxyBridge --proxy socks5://IP:PORT --rule \"app:*:*:TCP:PROXY\"" echo "" echo "For cleanup after crash:" diff --git a/Linux/src/ProxyBridge.h b/Linux/src/ProxyBridge.h index f885c68..023493e 100644 --- a/Linux/src/ProxyBridge.h +++ b/Linux/src/ProxyBridge.h @@ -1,7 +1,7 @@ #ifndef PROXYBRIDGE_H #define PROXYBRIDGE_H -#define PROXYBRIDGE_VERSION "4.0.0-Beta" +#define PROXYBRIDGE_VERSION "4.1.0-Beta" #include #include From 9669a3e32c32626d87cacc942f0c1980e5f72a44 Mon Sep 17 00:00:00 2001 From: Sourav Kalal Date: Sun, 8 Feb 2026 15:11:19 +0530 Subject: [PATCH 26/58] fixed #62 - IPv6 traffic now goes directly and not blocked --- Windows/src/ProxyBridge.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Windows/src/ProxyBridge.c b/Windows/src/ProxyBridge.c index b5d6750..d4b0a9d 100644 --- a/Windows/src/ProxyBridge.c +++ b/Windows/src/ProxyBridge.c @@ -106,6 +106,7 @@ static BOOL running = FALSE; static DWORD g_current_process_id = 0; static BOOL g_traffic_logging_enabled = TRUE; +static BOOL g_ipv6_warning_logged = FALSE; static char g_proxy_host[256] = ""; // Can be IP address or hostname static UINT16 g_proxy_port = 0; @@ -266,11 +267,23 @@ static DWORD WINAPI packet_processor(LPVOID arg) continue; } - WinDivertHelperParsePacket(packet, packet_len, &ip_header, NULL, NULL, + PWINDIVERT_IPV6HDR ipv6_header = NULL; + WinDivertHelperParsePacket(packet, packet_len, &ip_header, &ipv6_header, NULL, NULL, NULL, &tcp_header, &udp_header, NULL, NULL, NULL, NULL); if (ip_header == NULL) + { + if (ipv6_header != NULL) + { + if (!g_ipv6_warning_logged) + { + log_message("IPv6 traffic detected but not supported. IPv6 connections will not be proxied."); + g_ipv6_warning_logged = TRUE; + } + WinDivertSend(windivert_handle, packet, packet_len, NULL, &addr); + } continue; + } if (udp_header != NULL && tcp_header == NULL) { From 3406f9f51ee5cfbd8235599c5445a3477e523e40 Mon Sep 17 00:00:00 2001 From: Sourav Kalal Date: Sun, 8 Feb 2026 19:47:03 +0530 Subject: [PATCH 27/58] removed unwanted code for linux --- Linux/cli/main.c | 33 +++-- Linux/gui/gui_config.c | 2 +- Linux/gui/gui_logs.c | 8 +- Linux/gui/gui_rules.c | 74 +++++------ Linux/gui/gui_settings.c | 74 +++++------ Linux/gui/gui_utils.c | 4 +- Linux/gui/main.c | 46 +++---- Linux/src/ProxyBridge.c | 272 +++++++++++++++++++-------------------- 8 files changed, 254 insertions(+), 259 deletions(-) diff --git a/Linux/cli/main.c b/Linux/cli/main.c index b8807de..6781f14 100644 --- a/Linux/cli/main.c +++ b/Linux/cli/main.c @@ -227,7 +227,7 @@ static bool parse_proxy_url(const char* url, ProxyType* type, char* host, uint16 username[0] = '\0'; password[0] = '\0'; - // Parse type:// + // parse type:// char* scheme_end = strstr(buffer, "://"); if (scheme_end == NULL) { @@ -254,7 +254,7 @@ static bool parse_proxy_url(const char* url, ProxyType* type, char* host, uint16 return false; } - // Parse host:port[:username:password] + // parse host:port[:user:pass] char* parts[4]; int num_parts = 0; char* token = strtok(rest, ":"); @@ -298,7 +298,7 @@ static bool is_root(void) int main(int argc, char *argv[]) { - // Check for cleanup flag first - ignores all other args + // check cleanup flag for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--cleanup") == 0) @@ -315,7 +315,7 @@ int main(int argc, char *argv[]) int num_rules = 0; bool dns_via_proxy = true; - // Parse arguments + // parse args for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) @@ -371,7 +371,7 @@ int main(int argc, char *argv[]) show_banner(); - // Check root privileges + // need root if (!is_root()) { printf("\033[31m\nERROR: ProxyBridge requires root privileges!\033[0m\n"); @@ -379,7 +379,7 @@ int main(int argc, char *argv[]) return 1; } - // Parse proxy configuration + // parse proxy config ProxyType proxy_type; char proxy_host[256]; uint16_t proxy_port; @@ -389,11 +389,8 @@ int main(int argc, char *argv[]) if (!parse_proxy_url(proxy_url, &proxy_type, proxy_host, &proxy_port, proxy_username, proxy_password)) return 1; - // Setup callbacks based on verbose level - only enable what we need - // Verbose 0: No callbacks, no logging (most efficient) - // Verbose 1: Only log messages - // Verbose 2: Only connection events - // Verbose 3: Both logs and connections + // setup callbacks based on verbose + // 0=nothing 1=logs 2=connections 3=both if (verbose_level == 1 || verbose_level == 3) ProxyBridge_SetLogCallback(log_callback); @@ -405,10 +402,10 @@ int main(int argc, char *argv[]) else ProxyBridge_SetConnectionCallback(NULL); // Explicitly disable - // Enable traffic logging in C library only when needed (prevents unnecessary processing) + // turn on traffic logging when needed ProxyBridge_SetTrafficLoggingEnabled(verbose_level > 0); - // Display configuration + // show config printf("Proxy: %s://%s:%u\n", proxy_type == PROXY_TYPE_HTTP ? "http" : "socks5", proxy_host, proxy_port); @@ -418,7 +415,7 @@ int main(int argc, char *argv[]) printf("DNS via Proxy: %s\n", dns_via_proxy ? "Enabled" : "Disabled"); - // Configure proxy + // setup proxy if (!ProxyBridge_SetProxyConfig(proxy_type, proxy_host, proxy_port, proxy_username[0] ? proxy_username : "", proxy_password[0] ? proxy_password : "")) @@ -429,7 +426,7 @@ int main(int argc, char *argv[]) ProxyBridge_SetDnsViaProxy(dns_via_proxy); - // Add rules + // add rules if (num_rules > 0) { printf("Rules: %d\n", num_rules); @@ -469,7 +466,7 @@ int main(int argc, char *argv[]) printf("Use --rule to add proxy rules. See --help for examples.\n"); } - // Start ProxyBridge + // start proxybridge if (!ProxyBridge_Start()) { fprintf(stderr, "ERROR: Failed to start ProxyBridge\n"); @@ -485,13 +482,13 @@ int main(int argc, char *argv[]) signal(SIGABRT, signal_handler); // Catch abort signal(SIGBUS, signal_handler); // Catch bus error - // Main loop + // main loop while (keep_running) { sleep(1); } - // Cleanup + // cleanup ProxyBridge_Stop(); printf("ProxyBridge stopped.\n"); diff --git a/Linux/gui/gui_config.c b/Linux/gui/gui_config.c index ee184dc..0abf955 100644 --- a/Linux/gui/gui_config.c +++ b/Linux/gui/gui_config.c @@ -3,7 +3,7 @@ #define CONFIG_DIR "/etc/proxybridge" #define CONFIG_PATH "/etc/proxybridge/config.ini" -// save settings to file +// save config void save_config() { struct stat st = {0}; if (stat(CONFIG_DIR, &st) == -1) { diff --git a/Linux/gui/gui_logs.c b/Linux/gui/gui_logs.c index 3410269..3ff4c68 100644 --- a/Linux/gui/gui_logs.c +++ b/Linux/gui/gui_logs.c @@ -1,6 +1,6 @@ #include "gui.h" -// filter the text view based on user imput +// filter logs based on search static void filter_text_view(GtkTextBuffer *buffer, const char *text) { if (!buffer) return; @@ -18,7 +18,7 @@ static void filter_text_view(GtkTextBuffer *buffer, const char *text) { char *line_text = gtk_text_buffer_get_text(buffer, &line_start, &line_end, FALSE); - // case insensitive search + // search case insensitive char *lower_line = g_utf8_strdown(line_text, -1); char *lower_search = g_utf8_strdown(text, -1); @@ -64,7 +64,7 @@ static void free_connection_data(ConnectionData *data) { } } -// update logs safely from main thread +// update logs from main thread static gboolean update_log_gui(gpointer user_data) { LogData *data = (LogData *)user_data; if (!data) return FALSE; @@ -86,7 +86,7 @@ static gboolean update_log_gui(gpointer user_data) { return FALSE; // done } -// update connection info safely +// update conn info static gboolean update_connection_gui_append(gpointer user_data) { ConnectionData *data = (ConnectionData *)user_data; if (!data) return FALSE; diff --git a/Linux/gui/gui_rules.c b/Linux/gui/gui_rules.c index 9a8fde0..df625ed 100644 --- a/Linux/gui/gui_rules.c +++ b/Linux/gui/gui_rules.c @@ -3,7 +3,7 @@ static GtkWidget *rules_list_box = NULL; static GtkWidget *btn_select_all_header = NULL; -// forward decl +// forward static void refresh_rules_ui(); static void free_rule_data(RuleData *rule) { @@ -36,7 +36,7 @@ static void on_save_rule(GtkWidget *widget, gpointer data) { GtkWidget **widgets = (GtkWidget **)data; GtkWidget *dialog = widgets[0]; RuleData *edit_rule = (RuleData *)widgets[6]; // existing rule if present - + const char *proc = gtk_entry_get_text(GTK_ENTRY(widgets[1])); const char *hosts = gtk_entry_get_text(GTK_ENTRY(widgets[2])); const char *ports = gtk_entry_get_text(GTK_ENTRY(widgets[3])); @@ -44,18 +44,18 @@ static void on_save_rule(GtkWidget *widget, gpointer data) { RuleAction action = gtk_combo_box_get_active(GTK_COMBO_BOX(widgets[5])); if (strlen(proc) == 0) return; // need process name - + if (edit_rule) { - // update backend + // update backend and local copy ProxyBridge_EditRule(edit_rule->id, proc, hosts, ports, proto, action); - // update local + free(edit_rule->process_name); edit_rule->process_name = strdup(proc); free(edit_rule->target_hosts); edit_rule->target_hosts = strdup(hosts); free(edit_rule->target_ports); edit_rule->target_ports = strdup(ports); edit_rule->protocol = proto; edit_rule->action = action; } else { - // new rule + // add new rule uint32_t new_id = ProxyBridge_AddRule(proc, hosts, ports, proto, action); RuleData *new_rule = malloc(sizeof(RuleData)); new_rule->id = new_id; @@ -64,7 +64,7 @@ static void on_save_rule(GtkWidget *widget, gpointer data) { new_rule->target_ports = strdup(ports); new_rule->protocol = proto; new_rule->action = action; - new_rule->enabled = true; + new_rule->enabled = true; new_rule->selected = false; g_rules_list = g_list_append(g_rules_list, new_rule); } @@ -104,8 +104,8 @@ static void open_rule_dialog(RuleData *rule) { gtk_grid_set_row_spacing(GTK_GRID(grid), 8); gtk_grid_set_column_spacing(GTK_GRID(grid), 10); gtk_container_set_border_width(GTK_CONTAINER(grid), 15); - - // process input + + // proc input GtkWidget *proc_entry = gtk_entry_new(); GtkWidget *browse_btn = gtk_button_new_with_label("Browse..."); g_signal_connect(browse_btn, "clicked", G_CALLBACK(on_browse_clicked), proc_entry); @@ -115,13 +115,13 @@ static void open_rule_dialog(RuleData *rule) { GtkWidget *host_entry = gtk_entry_new(); GtkWidget *port_entry = gtk_entry_new(); - + // protocol list GtkWidget *proto_combo = gtk_combo_box_text_new(); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "TCP"); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "UDP"); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(proto_combo), "BOTH"); - + // action list GtkWidget *action_combo = gtk_combo_box_text_new(); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(action_combo), "PROXY"); @@ -135,8 +135,8 @@ static void open_rule_dialog(RuleData *rule) { gtk_combo_box_set_active(GTK_COMBO_BOX(proto_combo), rule->protocol); gtk_combo_box_set_active(GTK_COMBO_BOX(action_combo), rule->action); } else { - gtk_entry_set_text(GTK_ENTRY(port_entry), "*"); - gtk_entry_set_text(GTK_ENTRY(host_entry), "*"); + gtk_entry_set_text(GTK_ENTRY(port_entry), "*"); + gtk_entry_set_text(GTK_ENTRY(host_entry), "*"); gtk_combo_box_set_active(GTK_COMBO_BOX(proto_combo), 2); // BOTH default gtk_combo_box_set_active(GTK_COMBO_BOX(action_combo), 0); // PROXY default } @@ -144,7 +144,7 @@ static void open_rule_dialog(RuleData *rule) { int row = 0; gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Process Name:"), 0, row, 1, 1); gtk_grid_attach(GTK_GRID(grid), proc_box, 1, row, 1, 1); row++; - + GtkWidget *proc_hint = gtk_label_new("Example: firefox; chrome; /usr/bin/wget"); gtk_style_context_add_class(gtk_widget_get_style_context(proc_hint), "dim-label"); gtk_widget_set_halign(proc_hint, GTK_ALIGN_START); @@ -152,7 +152,7 @@ static void open_rule_dialog(RuleData *rule) { gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target Host:"), 0, row, 1, 1); gtk_grid_attach(GTK_GRID(grid), host_entry, 1, row, 1, 1); row++; - + GtkWidget *host_hint = gtk_label_new("Example: 192.168.1.*; 10.0.0.1-50; *"); gtk_style_context_add_class(gtk_widget_get_style_context(host_hint), "dim-label"); gtk_widget_set_halign(host_hint, GTK_ALIGN_START); @@ -160,25 +160,25 @@ static void open_rule_dialog(RuleData *rule) { gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target Port:"), 0, row, 1, 1); gtk_grid_attach(GTK_GRID(grid), port_entry, 1, row, 1, 1); row++; - + GtkWidget *port_hint = gtk_label_new("Example: 80; 443; 8000-8080; *"); gtk_style_context_add_class(gtk_widget_get_style_context(port_hint), "dim-label"); gtk_widget_set_halign(port_hint, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID(grid), port_hint, 1, row, 1, 1); row++; - + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Protocol:"), 0, row, 1, 1); gtk_grid_attach(GTK_GRID(grid), proto_combo, 1, row, 1, 1); row++; - + gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Action:"), 0, row, 1, 1); gtk_grid_attach(GTK_GRID(grid), action_combo, 1, row, 1, 1); row++; gtk_container_add(GTK_CONTAINER(content), grid); - + GtkWidget *save_btn = gtk_button_new_with_label("Save"); GtkWidget *cancel_btn = gtk_button_new_with_label("Cancel"); gtk_dialog_add_action_widget(GTK_DIALOG(dialog), cancel_btn, GTK_RESPONSE_CANCEL); gtk_dialog_add_action_widget(GTK_DIALOG(dialog), save_btn, GTK_RESPONSE_ACCEPT); - + GtkWidget **data = malloc(7 * sizeof(GtkWidget*)); data[0] = dialog; data[1] = proc_entry; @@ -187,15 +187,15 @@ static void open_rule_dialog(RuleData *rule) { data[4] = proto_combo; data[5] = action_combo; data[6] = (GtkWidget*)rule; - + g_signal_connect(save_btn, "clicked", G_CALLBACK(on_save_rule), data); - g_signal_connect(cancel_btn, "clicked", G_CALLBACK(gtk_widget_destroy), NULL); + g_signal_connect(cancel_btn, "clicked", G_CALLBACK(gtk_widget_destroy), NULL); gtk_widget_show_all(dialog); } static void on_rule_edit(GtkWidget *widget, gpointer data) { on_proxy_rules_clicked(NULL, NULL); // Re-open if closed? Usually modal. - // Wait, on_rule_edit is clicked FROM the rules list. + // Wait, on_rule_edit clicked from rules list already open_rule_dialog((RuleData *)data); } @@ -274,14 +274,14 @@ static void on_rule_import_clicked(GtkWidget *widget, gpointer data) { char *end = strchr(curr, '}'); if (!end) break; char saved = *end; *end = '\0'; - + char *proc = extract_sub_json_str(curr, "processNames"); char *host = extract_sub_json_str(curr, "targetHosts"); char *port = extract_sub_json_str(curr, "targetPorts"); char *proto_s = extract_sub_json_str(curr, "protocol"); char *act_s = extract_sub_json_str(curr, "action"); bool en = extract_sub_json_bool(curr, "enabled"); - + if (proc && host && port && proto_s && act_s) { RuleProtocol p = RULE_PROTOCOL_BOTH; if (strcmp(proto_s, "TCP") == 0) p = RULE_PROTOCOL_TCP; @@ -289,7 +289,7 @@ static void on_rule_import_clicked(GtkWidget *widget, gpointer data) { RuleAction a = RULE_ACTION_PROXY; if (strcmp(act_s, "DIRECT") == 0) a = RULE_ACTION_DIRECT; else if (strcmp(act_s, "BLOCK") == 0) a = RULE_ACTION_BLOCK; - + uint32_t nid = ProxyBridge_AddRule(proc, host, port, p, a); if (!en) ProxyBridge_DisableRule(nid); RuleData *nd = malloc(sizeof(RuleData)); @@ -354,7 +354,7 @@ static void refresh_rules_ui() { } gtk_button_set_label(GTK_BUTTON(btn_select_all_header), all_selected ? "Deselect All" : "Select All"); } - + GtkWidget *grid = gtk_grid_new(); gtk_grid_set_row_spacing(GTK_GRID(grid), 10); gtk_grid_set_column_spacing(GTK_GRID(grid), 15); @@ -386,7 +386,7 @@ static void refresh_rules_ui() { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk), r->enabled); g_signal_connect(chk, "toggled", G_CALLBACK(on_rule_toggle), r); gtk_grid_attach(GTK_GRID(grid), chk, 1, row, 1, 1); - + GtkWidget *act_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2); GtkWidget *btn_edit = gtk_button_new_with_label("Edit"); g_signal_connect(btn_edit, "clicked", G_CALLBACK(on_rule_edit), r); @@ -395,21 +395,21 @@ static void refresh_rules_ui() { gtk_box_pack_start(GTK_BOX(act_box), btn_edit, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(act_box), btn_del, FALSE, FALSE, 0); gtk_grid_attach(GTK_GRID(grid), act_box, 2, row, 1, 1); - + char sr_str[16]; snprintf(sr_str, sizeof(sr_str), "%d", sr_counter++); gtk_grid_attach(GTK_GRID(grid), gtk_label_new(sr_str), 3, row, 1, 1); - + GtkWidget *l_proc = gtk_label_new(r->process_name); gtk_widget_set_halign(l_proc, GTK_ALIGN_START); gtk_label_set_ellipsize(GTK_LABEL(l_proc), PANGO_ELLIPSIZE_END); gtk_grid_attach(GTK_GRID(grid), l_proc, 4, row, 1, 1); - + GtkWidget *l_host = gtk_label_new(r->target_hosts); gtk_widget_set_halign(l_host, GTK_ALIGN_START); gtk_label_set_ellipsize(GTK_LABEL(l_host), PANGO_ELLIPSIZE_END); gtk_grid_attach(GTK_GRID(grid), l_host, 5, row, 1, 1); - + const char* proto_strs[] = {"TCP", "UDP", "BOTH"}; gtk_grid_attach(GTK_GRID(grid), gtk_label_new(proto_strs[r->protocol]), 6, row, 1, 1); - + const char* action_strs[] = {"PROXY", "DIRECT", "BLOCK"}; GtkWidget *l_act = gtk_label_new(action_strs[r->action]); GtkStyleContext *context = gtk_widget_get_style_context(l_act); @@ -417,7 +417,7 @@ static void refresh_rules_ui() { else if (r->action == RULE_ACTION_DIRECT) gtk_style_context_add_class(context, "info"); else gtk_style_context_add_class(context, "warning"); gtk_grid_attach(GTK_GRID(grid), l_act, 7, row, 1, 1); - + row++; gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, 8, 1); row++; } @@ -432,7 +432,7 @@ void on_proxy_rules_clicked(GtkWidget *widget, gpointer data) { GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); gtk_container_set_border_width(GTK_CONTAINER(vbox), 20); gtk_container_add(GTK_CONTAINER(dialog), vbox); - + GtkWidget *header_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); GtkWidget *title = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(title), "Process Rules"); @@ -457,13 +457,13 @@ void on_proxy_rules_clicked(GtkWidget *widget, gpointer data) { gtk_box_pack_end(GTK_BOX(header_box), export_btn, FALSE, FALSE, 5); gtk_box_pack_end(GTK_BOX(header_box), import_btn, FALSE, FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), header_box, FALSE, FALSE, 0); - + GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL); gtk_widget_set_vexpand(scrolled, TRUE); rules_list_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); gtk_container_add(GTK_CONTAINER(scrolled), rules_list_box); gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0); - + refresh_rules_ui(); gtk_widget_show_all(dialog); } diff --git a/Linux/gui/gui_settings.c b/Linux/gui/gui_settings.c index c034cf1..3268f9c 100644 --- a/Linux/gui/gui_settings.c +++ b/Linux/gui/gui_settings.c @@ -1,12 +1,12 @@ #include "gui.h" -// settings view logic +// settings stuff static gboolean on_test_done(gpointer user_data) { TestResultData *data = (TestResultData *)user_data; gtk_text_buffer_set_text(data->buffer, data->result_text, -1); gtk_widget_set_sensitive(data->btn, TRUE); - + free(data->result_text); free(data); return FALSE; @@ -14,20 +14,20 @@ static gboolean on_test_done(gpointer user_data) { static gpointer run_test_thread(gpointer user_data) { struct TestRunnerData *req = (struct TestRunnerData *)user_data; - + char *buffer = malloc(4096); memset(buffer, 0, 4096); - - // run the test + + // run test ProxyBridge_TestConnection(req->host, req->port, buffer, 4096); - + TestResultData *res = malloc(sizeof(TestResultData)); res->result_text = buffer; res->buffer = req->ui_info->output_buffer; res->btn = req->ui_info->test_btn; - + g_idle_add(on_test_done, res); - + free(req->host); free(req); return NULL; @@ -35,48 +35,48 @@ static gpointer run_test_thread(gpointer user_data) { static void on_start_test_clicked(GtkWidget *widget, gpointer data) { ConfigInfo *info = (ConfigInfo *)data; - - // validate inputs + + // check inputs const char *ip_text = gtk_entry_get_text(GTK_ENTRY(info->ip_entry)); const char *port_text = gtk_entry_get_text(GTK_ENTRY(info->port_entry)); - + if (!ip_text || strlen(ip_text) == 0 || strspn(port_text, "0123456789") != strlen(port_text) || strlen(port_text) == 0) { gtk_text_buffer_set_text(info->output_buffer, "Error: Invalid Proxy IP or Port.", -1); return; } - + // save config ProxyType type = (gtk_combo_box_get_active(GTK_COMBO_BOX(info->type_combo)) == 0) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; int port = (int)safe_strtol(port_text); const char *user = gtk_entry_get_text(GTK_ENTRY(info->user_entry)); const char *pass = gtk_entry_get_text(GTK_ENTRY(info->pass_entry)); - + ProxyBridge_SetProxyConfig(type, ip_text, port, user, pass); - + // get target const char *t_host = gtk_entry_get_text(GTK_ENTRY(info->test_host)); const char *t_port_s = gtk_entry_get_text(GTK_ENTRY(info->test_port)); - + if (!t_host || strlen(t_host) == 0) t_host = "google.com"; int t_port = (int)safe_strtol(t_port_s); if (t_port <= 0) t_port = 80; - + // update gui gtk_text_buffer_set_text(info->output_buffer, "Testing connection... Please wait...", -1); gtk_widget_set_sensitive(info->test_btn, FALSE); - + // start thread struct TestRunnerData *req = malloc(sizeof(struct TestRunnerData)); req->host = strdup(t_host); req->port = t_port; req->ui_info = info; - + GThread *thread = g_thread_new("test_conn", run_test_thread, req); g_thread_unref(thread); } void on_proxy_configure(GtkWidget *widget, gpointer data) { - ConfigInfo info; + ConfigInfo info; GtkWidget *content_area; GtkWidget *grid; @@ -87,7 +87,7 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL); - // make it wider + // wider window gtk_window_set_default_size(GTK_WINDOW(info.dialog), 600, 500); content_area = gtk_dialog_get_content_area(GTK_DIALOG(info.dialog)); @@ -97,22 +97,22 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { gtk_container_set_border_width(GTK_CONTAINER(grid), 10); gtk_box_pack_start(GTK_BOX(content_area), grid, TRUE, TRUE, 0); - // type selection + // proxy type dropdown gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Type:"), 0, 0, 1, 1); info.type_combo = gtk_combo_box_text_new(); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(info.type_combo), "HTTP"); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(info.type_combo), "SOCKS5"); gtk_combo_box_set_active(GTK_COMBO_BOX(info.type_combo), g_proxy_type == PROXY_TYPE_HTTP ? 0 : 1); - gtk_grid_attach(GTK_GRID(grid), info.type_combo, 1, 0, 3, 1); + gtk_grid_attach(GTK_GRID(grid), info.type_combo, 1, 0, 3, 1); - // ip address + // ip field gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Host:"), 0, 1, 1, 1); info.ip_entry = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(info.ip_entry), g_proxy_ip); gtk_widget_set_hexpand(info.ip_entry, TRUE); gtk_grid_attach(GTK_GRID(grid), info.ip_entry, 1, 1, 3, 1); - // port number + // port field gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Port:"), 0, 2, 1, 1); info.port_entry = gtk_entry_new(); if (g_proxy_port != 0) { @@ -122,7 +122,7 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { } gtk_grid_attach(GTK_GRID(grid), info.port_entry, 1, 2, 3, 1); - // username + // user field gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Username:"), 0, 3, 1, 1); info.user_entry = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(info.user_entry), g_proxy_user); @@ -135,42 +135,42 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { gtk_entry_set_text(GTK_ENTRY(info.pass_entry), g_proxy_pass); gtk_grid_attach(GTK_GRID(grid), info.pass_entry, 1, 4, 3, 1); - // test connection section + // test section gtk_grid_attach(GTK_GRID(grid), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 5, 4, 1); - + GtkWidget *test_label = gtk_label_new("Test Connection"); gtk_label_set_use_markup(GTK_LABEL(test_label), TRUE); gtk_widget_set_halign(test_label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID(grid), test_label, 0, 6, 4, 1); - // target host + // target gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target:"), 0, 7, 1, 1); info.test_host = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(info.test_host), "google.com"); gtk_grid_attach(GTK_GRID(grid), info.test_host, 1, 7, 1, 1); - - // Target Port + + // target port gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Port:"), 2, 7, 1, 1); info.test_port = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(info.test_port), "80"); gtk_widget_set_size_request(info.test_port, 80, -1); gtk_grid_attach(GTK_GRID(grid), info.test_port, 3, 7, 1, 1); - // Start Test Button + // test button info.test_btn = gtk_button_new_with_label("Start Test"); g_signal_connect(info.test_btn, "clicked", G_CALLBACK(on_start_test_clicked), &info); gtk_grid_attach(GTK_GRID(grid), info.test_btn, 0, 8, 4, 1); - // Output Log + // output log GtkWidget *out_scroll = gtk_scrolled_window_new(NULL, NULL); gtk_widget_set_size_request(out_scroll, -1, 150); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(out_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - + GtkWidget *out_view = gtk_text_view_new(); gtk_text_view_set_editable(GTK_TEXT_VIEW(out_view), FALSE); gtk_text_view_set_monospace(GTK_TEXT_VIEW(out_view), TRUE); info.output_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(out_view)); - + gtk_container_add(GTK_CONTAINER(out_scroll), out_view); gtk_grid_attach(GTK_GRID(grid), out_scroll, 0, 9, 4, 1); @@ -180,7 +180,7 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { while (TRUE) { if (gtk_dialog_run(GTK_DIALOG(info.dialog)) != GTK_RESPONSE_ACCEPT) break; - // Validation + // check const char *ip_text = gtk_entry_get_text(GTK_ENTRY(info.ip_entry)); const char *port_text = gtk_entry_get_text(GTK_ENTRY(info.port_entry)); @@ -200,7 +200,7 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { continue; } - // Save + // save g_proxy_type = (gtk_combo_box_get_active(GTK_COMBO_BOX(info.type_combo)) == 0) ? PROXY_TYPE_HTTP : PROXY_TYPE_SOCKS5; g_strlcpy(g_proxy_ip, ip_text, sizeof(g_proxy_ip)); g_proxy_port = p; @@ -208,7 +208,7 @@ void on_proxy_configure(GtkWidget *widget, gpointer data) { g_strlcpy(g_proxy_pass, gtk_entry_get_text(GTK_ENTRY(info.pass_entry)), sizeof(g_proxy_pass)); ProxyBridge_SetProxyConfig(g_proxy_type, g_proxy_ip, g_proxy_port, g_proxy_user, g_proxy_pass); - + save_config(); char status_msg[512]; diff --git a/Linux/gui/gui_utils.c b/Linux/gui/gui_utils.c index 59e0821..ae69d29 100644 --- a/Linux/gui/gui_utils.c +++ b/Linux/gui/gui_utils.c @@ -9,7 +9,7 @@ long safe_strtol(const char *nptr) { return val; } -// show simple popup +// show popup msg void show_message(GtkWindow *parent, GtkMessageType type, const char *format, ...) { va_list args; va_start(args, format); @@ -26,7 +26,7 @@ void show_message(GtkWindow *parent, GtkMessageType type, const char *format, .. g_free(msg); } -// limit log size so it doesnt eat ram +// keep log size under control so we dont use too much ram void trim_buffer_lines(GtkTextBuffer *buffer, int max_lines) { if (gtk_text_buffer_get_line_count(buffer) > max_lines) { GtkTextIter start, next; diff --git a/Linux/gui/main.c b/Linux/gui/main.c index 025cf65..c51d5ce 100644 --- a/Linux/gui/main.c +++ b/Linux/gui/main.c @@ -1,6 +1,6 @@ #include "gui.h" -// ui widgets +// widgets GtkWidget *window; GtkTextView *conn_view; GtkTextBuffer *conn_buffer; @@ -42,15 +42,15 @@ static void on_create_update_script_and_run() { if (!tmp_dir) { fprintf(stderr, "Failed to create temp directory for update.\n"); exit(1); } char script_path[512]; snprintf(script_path, sizeof(script_path), "%s/deploy.sh", tmp_dir); - + pid_t pid = fork(); - if (pid == -1) { fprintf(stderr, "Fork failed.\n"); exit(1); } - else if (pid == 0) { execlp("curl", "curl", "-s", "-o", script_path, script_url, NULL); _exit(127); } + if (pid == -1) { fprintf(stderr, "Fork failed.\n"); exit(1); } + else if (pid == 0) { execlp("curl", "curl", "-s", "-o", script_path, script_url, NULL); _exit(127); } else { int status; waitpid(pid, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { fprintf(stderr, "Failed to download update script.\n"); exit(1); } } - + if (chmod(script_path, S_IRWXU) != 0) { perror("chmod failed"); exit(1); } execl("/bin/bash", "bash", script_path, NULL); - exit(0); + exit(0); } static void on_check_update(GtkWidget *widget, gpointer data) { @@ -65,14 +65,14 @@ static void on_check_update(GtkWidget *widget, gpointer data) { g_free(cmd); if (!result) { show_message(GTK_WINDOW(window), GTK_MESSAGE_ERROR, "Failed to launch release check: %s", error ? error->message : "Unknown"); if (error) g_error_free(error); return; } if (exit_status != 0 || !standard_output || strlen(standard_output) == 0) { show_message(GTK_WINDOW(window), GTK_MESSAGE_ERROR, "Update check failed (Exit: %d).", exit_status); g_free(standard_output); g_free(standard_error); return; } - + char *tag_name = extract_sub_json_str(standard_output, "tag_name"); g_free(standard_output); g_free(standard_error); if (!tag_name) { show_message(GTK_WINDOW(window), GTK_MESSAGE_WARNING, "Could not parse version info."); return; } char *current_tag = g_strdup_printf("v%s", PROXYBRIDGE_VERSION); - - if (strcmp(tag_name, current_tag) == 0) { show_message(GTK_WINDOW(window), GTK_MESSAGE_INFO, "You are using the latest version (%s).", PROXYBRIDGE_VERSION); } + + if (strcmp(tag_name, current_tag) == 0) { show_message(GTK_WINDOW(window), GTK_MESSAGE_INFO, "You are using the latest version (%s).", PROXYBRIDGE_VERSION); } else { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "New version %s is available!\nCurrent: %s\n\nUpdate now?", tag_name, PROXYBRIDGE_VERSION); gtk_dialog_add_button(GTK_DIALOG(dialog), "Download Now", GTK_RESPONSE_ACCEPT); @@ -125,10 +125,10 @@ int main(int argc, char *argv[]) { if (getuid() != 0) { gtk_init(&argc, &argv); show_message(NULL, GTK_MESSAGE_ERROR, "ProxyBridge must be run as root (sudo)."); return 1; } setenv("GSETTINGS_BACKEND", "memory", 1); - - // load saved config + + // load config from file load_config(); - + gtk_init(&argc, &argv); GtkSettings *settings = gtk_settings_get_default(); @@ -142,27 +142,27 @@ int main(int argc, char *argv[]) { GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_container_add(GTK_CONTAINER(window), vbox); - // menu setup + // setup menu GtkWidget *menubar = gtk_menu_bar_new(); GtkWidget *proxy_menu_item = gtk_menu_item_new_with_label("Proxy"); GtkWidget *proxy_menu = gtk_menu_new(); GtkWidget *config_item = gtk_menu_item_new_with_label("Proxy Settings"); GtkWidget *rules_item = gtk_menu_item_new_with_label("Proxy Rules"); - + GtkWidget *log_check_item = gtk_check_menu_item_new_with_label("Enable Traffic Logging"); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(log_check_item), g_chk_logging); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(log_check_item), g_chk_logging); g_signal_connect(log_check_item, "toggled", G_CALLBACK(on_log_traffic_toggled), NULL); GtkWidget *dns_check_item = gtk_check_menu_item_new_with_label("DNS via Proxy"); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(dns_check_item), g_chk_dns); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(dns_check_item), g_chk_dns); g_signal_connect(dns_check_item, "toggled", G_CALLBACK(on_dns_proxy_toggled), NULL); GtkWidget *exit_item = gtk_menu_item_new_with_label("Exit"); - + g_signal_connect(config_item, "activate", G_CALLBACK(on_proxy_configure), NULL); g_signal_connect(rules_item, "activate", G_CALLBACK(on_proxy_rules_clicked), NULL); g_signal_connect(exit_item, "activate", G_CALLBACK(on_window_destroy), NULL); - + gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), config_item); gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), rules_item); gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), gtk_separator_menu_item_new()); @@ -171,7 +171,7 @@ int main(int argc, char *argv[]) { gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), gtk_separator_menu_item_new()); gtk_menu_shell_append(GTK_MENU_SHELL(proxy_menu), exit_item); gtk_menu_item_set_submenu(GTK_MENU_ITEM(proxy_menu_item), proxy_menu); - + GtkWidget *about_menu_item = gtk_menu_item_new_with_label("About"); GtkWidget *about_menu = gtk_menu_new(); GtkWidget *about_child_item = gtk_menu_item_new_with_label("About"); @@ -190,7 +190,7 @@ int main(int argc, char *argv[]) { GtkWidget *notebook = gtk_notebook_new(); gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); - // active connections tab + // connections tab GtkWidget *conn_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); gtk_container_set_border_width(GTK_CONTAINER(conn_vbox), 5); GtkWidget *conn_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); @@ -239,16 +239,16 @@ int main(int argc, char *argv[]) { status_context_id = gtk_statusbar_get_context_id(GTK_STATUSBAR(status_bar), "Status"); gtk_box_pack_start(GTK_BOX(vbox), status_bar, FALSE, FALSE, 0); - // start it up + // start ProxyBridge_SetLogCallback(lib_log_callback); ProxyBridge_SetConnectionCallback(lib_connection_callback); ProxyBridge_SetTrafficLoggingEnabled(g_chk_logging); ProxyBridge_SetDnsViaProxy(g_chk_dns); - + if (ProxyBridge_Start()) { // apply config ProxyBridge_SetProxyConfig(g_proxy_type, g_proxy_ip, g_proxy_port, g_proxy_user, g_proxy_pass); - + // restore rules for (GList *l = g_rules_list; l != NULL; l = l->next) { RuleData *r = (RuleData *)l->data; diff --git a/Linux/src/ProxyBridge.c b/Linux/src/ProxyBridge.c index 514e935..0bed311 100644 --- a/Linux/src/ProxyBridge.c +++ b/Linux/src/ProxyBridge.c @@ -38,14 +38,14 @@ #define HTTP_BUFFER_SIZE 1024 #define LOG_BUFFER_SIZE 1024 -// Secure replacement for system() to avoid shell injection and adhere to secure coding practices +// safe way to run commands without shell injection issues static int run_command_v(const char *cmd_path, char *const argv[]) { pid_t pid = fork(); if (pid == -1) { return -1; } else if (pid == 0) { - // Child - // Redirect stdout/stderr to /dev/null + // child process + // send output to /dev/null so it doesnt clutter int fd = open("/dev/null", O_WRONLY); if (fd >= 0) { dup2(fd, STDOUT_FILENO); @@ -53,9 +53,9 @@ static int run_command_v(const char *cmd_path, char *const argv[]) { close(fd); } execvp(cmd_path, argv); - _exit(127); // Access denied or not found + _exit(127); // command not found or no perms } else { - // Parent + // parent waits for child int status; waitpid(pid, &status, 0); if (WIFEXITED(status)) { @@ -65,9 +65,9 @@ static int run_command_v(const char *cmd_path, char *const argv[]) { } } -// Helper to run simple commands without arguments or simplified interface +// run iptables commands easier static int run_iptables_cmd(const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *arg5, const char *arg6, const char *arg7, const char *arg8, const char *arg9, const char *arg10, const char *arg11, const char *arg12, const char *arg13, const char *arg14) { - // Construct argv excluding NULLs + // build argv array skipping null args const char *argv[17]; int i = 0; argv[i++] = "iptables"; @@ -86,11 +86,11 @@ static int run_iptables_cmd(const char *arg1, const char *arg2, const char *arg3 if (arg13) argv[i++] = arg13; if (arg14) argv[i++] = arg14; argv[i] = NULL; - + return run_command_v("iptables", (char **)argv); } -// Helper for strtol to replace atoi +// convert string to int safely static int safe_atoi(const char *str) { if (!str) return 0; char *endptr; @@ -175,7 +175,7 @@ static bool g_has_active_rules = false; static bool running = false; static uint32_t g_current_process_id = 0; -// UDP relay globals +// udp relay stuff static int udp_relay_socket = -1; static int socks5_udp_control_socket = -1; static int socks5_udp_send_socket = -1; @@ -248,7 +248,7 @@ static bool parse_token_list(const char *list, const char *delimiters, token_mat { token = skip_whitespace(token); - // Trim trailing whitespace + // remove spaces at end size_t tlen = strlen(token); while (tlen > 0 && (token[tlen - 1] == ' ' || token[tlen - 1] == '\t' || token[tlen-1] == '\r' || token[tlen-1] == '\n')) { @@ -336,28 +336,28 @@ static void cache_pid(uint32_t src_ip, uint16_t src_port, uint32_t pid, bool is_ static void clear_pid_cache(void); static void update_has_active_rules(void); -// Find PID that owns a socket inode by scanning /proc/[pid]/fd/ -// Uses UID hint from netlink to skip non-matching processes +// find which process owns a socket by checking /proc +// uses uid hint to skip processes we dont need to check static uint32_t find_pid_from_inode(unsigned long target_inode, uint32_t uid_hint) { - // Build the expected link target string once + // build the socket string we're looking for char expected[64]; int expected_len = snprintf(expected, sizeof(expected), "socket:[%lu]", target_inode); - + DIR *proc_dir = opendir("/proc"); if (!proc_dir) return 0; - + uint32_t pid = 0; struct dirent *proc_entry; - + while ((proc_entry = readdir(proc_dir)) != NULL) { - // Skip non-numeric entries (not PID dirs) + // skip stuff that aint a pid folder if (proc_entry->d_type != DT_DIR || !isdigit(proc_entry->d_name[0])) continue; - - // If we have a UID hint from netlink, skip processes owned by other users - // This dramatically reduces the /proc/*/fd scan scope + + // if we know the user id, skip other users processes + // makes this way faster cuz less folders to check if (uid_hint != (uint32_t)-1) { struct stat proc_stat; char proc_path[280]; @@ -365,22 +365,22 @@ static uint32_t find_pid_from_inode(unsigned long target_inode, uint32_t uid_hin if (stat(proc_path, &proc_stat) == 0 && proc_stat.st_uid != uid_hint) continue; } - + char fd_path[280]; snprintf(fd_path, sizeof(fd_path), "/proc/%s/fd", proc_entry->d_name); DIR *fd_dir = opendir(fd_path); if (!fd_dir) continue; - + struct dirent *fd_entry; while ((fd_entry = readdir(fd_dir)) != NULL) { if (fd_entry->d_name[0] == '.') continue; - + char link_path[560]; snprintf(link_path, sizeof(link_path), "/proc/%s/fd/%s", proc_entry->d_name, fd_entry->d_name); - + char link_target[64]; ssize_t link_len = readlink(link_path, link_target, sizeof(link_target) - 1); if (link_len == expected_len) { @@ -399,8 +399,8 @@ static uint32_t find_pid_from_inode(unsigned long target_inode, uint32_t uid_hin return pid; } -// Optimized PID lookup using netlink sock_diag -// Uses exact netlink query (no DUMP) for TCP, targeted DUMP for UDP +// fast pid lookup using netlink +// tcp uses exact query, udp needs dump static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_port, bool is_udp) { uint32_t cached_pid = get_cached_pid(src_ip, src_port, is_udp); @@ -411,7 +411,7 @@ static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_por if (fd < 0) return 0; - // Set a short timeout to avoid blocking packet processing + // short timeout so we dont block packet flow struct timeval tv = {0, 100000}; // 100ms setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); @@ -426,8 +426,8 @@ static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_por req.r.sdiag_family = AF_INET; req.r.sdiag_protocol = is_udp ? IPPROTO_UDP : IPPROTO_TCP; - // For UDP: must use DUMP (connectionless sockets don't support exact lookup) - // For TCP: use DUMP with state filter for SYN_SENT + ESTABLISHED only + // udp needs dump cuz no connection state to match on + // tcp can filter to only syn_sent and established states req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; if (!is_udp) req.r.idiag_states = (1 << 2) | (1 << 3); // SYN_SENT(2) + ESTABLISHED(3) only @@ -465,7 +465,7 @@ static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_por if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY) { struct inet_diag_msg *r = NLMSG_DATA(h); - // Match src IP and port + // check if this is our socket if (r->id.idiag_src[0] == src_ip && ntohs(r->id.idiag_sport) == src_port) { target_inode = r->idiag_inode; target_uid = r->idiag_uid; // UID to narrow /proc scan @@ -480,21 +480,21 @@ static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_por nl_done: close(fd); - // If netlink found the socket, find PID from inode - // Skip the redundant /proc/net/tcp scan - netlink already gave us the inode + // if netlink found it, now find pid from inode + // no need to scan /proc/net/tcp since we got inode already if (found && target_inode != 0) { pid = find_pid_from_inode(target_inode, target_uid); } - // Fallback for UDP: scan /proc/net/udp if netlink didn't find the socket - // This happens when UDP socket uses sendto() without connect() + // fallback for udp if netlink didnt find it + // happens when app uses sendto without connecting socket first if (!found && is_udp) { const char* udp_files[] = {"/proc/net/udp", "/proc/net/udp6"}; for (int file_idx = 0; file_idx < 2 && pid == 0; file_idx++) { FILE *fp = fopen(udp_files[file_idx], "r"); if (!fp) continue; - + char line[512]; if (!fgets(line, sizeof(line), fp)) { // skip header fclose(fp); @@ -504,7 +504,7 @@ static uint32_t get_process_id_from_connection(uint32_t src_ip, uint16_t src_por unsigned int local_addr, local_port; unsigned long inode; int uid_val; - + if (sscanf(line, "%*d: %X:%X %*X:%*X %*X %*X:%*X %*X:%*X %*X %d %*d %lu", &local_addr, &local_port, &uid_val, &inode) == 4) { if (local_port == src_port && inode != 0) { @@ -527,8 +527,8 @@ static bool get_process_name_from_pid(uint32_t pid, char *name, size_t name_size if (pid == 0) return false; - // PID 1 is reserved for init/systemd on Linux - // Similar to Windows PID 4 (System), some system processes use PID 1 + // pid 1 is always init/systemd + // kinda like windows pid 4 for system stuff if (pid == 1) { snprintf(name, name_size, "systemd"); @@ -537,11 +537,11 @@ static bool get_process_name_from_pid(uint32_t pid, char *name, size_t name_size char path[64]; snprintf(path, sizeof(path), "/proc/%u/exe", pid); - + ssize_t len = readlink(path, name, name_size - 1); if (len < 0) return false; - + name[len] = '\0'; return true; } @@ -745,25 +745,25 @@ static uint32_t resolve_hostname(const char *hostname) static bool is_broadcast_or_multicast(uint32_t ip) { - // Localhost: 127.0.0.0/8 (127.x.x.x) + // localhost uint8_t first_octet = (ip >> 0) & 0xFF; if (first_octet == 127) return true; - // APIPA (Link-Local): 169.254.0.0/16 (169.254.x.x) + // link-local apipa stuff uint8_t second_octet = (ip >> 8) & 0xFF; if (first_octet == 169 && second_octet == 254) return true; - // Broadcast: 255.255.255.255 + // broadcast if (ip == 0xFFFFFFFF) return true; - // x.x.x.255 (subnet broadcasts) + // subnet broadcast if ((ip & 0xFF000000) == 0xFF000000) return true; - // Multicast: 224.0.0.0 - 239.255.255.255 (first octet 224-239) + // multicast range if (first_octet >= 224 && first_octet <= 239) return true; @@ -923,7 +923,7 @@ static int socks5_connect(int s, uint32_t dest_ip, uint16_t dest_port) memcpy(buf + 2, g_proxy_username, ulen); buf[2 + ulen] = (unsigned char)plen; memcpy(buf + 3 + ulen, g_proxy_password, plen); - + if (send(s, buf, 3 + ulen + plen, MSG_NOSIGNAL) != (ssize_t)(3 + ulen + plen)) { log_message("socks5 failed to send credentials"); @@ -981,7 +981,7 @@ static int http_connect(int s, uint32_t dest_ip, uint16_t dest_port) if (g_proxy_username[0] != '\0') { - // Base64 encode username:password + // encode user:pass in base64 static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; char auth_raw[512]; int auth_raw_len = snprintf(auth_raw, sizeof(auth_raw), "%s:%s", g_proxy_username, g_proxy_password); @@ -1033,9 +1033,9 @@ static int http_connect(int s, uint32_t dest_ip, uint16_t dest_port) return 0; } -// ===== EPOLL RELAY FUNCTIONS (Production) ===== +// relay functions for production use -// Windows-style connection handler - blocks on connect and handshake, then calls transfer_handler +// connection handler like windows - blocks on connect then transfers data static void* connection_handler(void *arg) { connection_config_t *config = (connection_config_t *)arg; @@ -1048,11 +1048,11 @@ static void* connection_handler(void *arg) free(config); - // Use cached proxy IP (resolved once at config time) + // use cached proxy ip we resolved earlier proxy_ip = g_proxy_ip_cached; if (proxy_ip == 0) { - // Try resolving again as fallback + // try resolving again if cache failed proxy_ip = resolve_hostname(g_proxy_host); if (proxy_ip == 0) { @@ -1084,7 +1084,7 @@ static void* connection_handler(void *arg) return NULL; } - // Do blocking handshake + // do handshake blocking if (g_proxy_type == PROXY_TYPE_SOCKS5) { if (socks5_connect(proxy_sock, dest_ip, dest_port) != 0) @@ -1104,7 +1104,7 @@ static void* connection_handler(void *arg) } } - // Create transfer config + // setup transfer config transfer_config_t *transfer_config = (transfer_config_t *)malloc(sizeof(transfer_config_t)); if (transfer_config == NULL) { @@ -1116,15 +1116,15 @@ static void* connection_handler(void *arg) transfer_config->from_socket = client_sock; transfer_config->to_socket = proxy_sock; - // Do bidirectional transfer in this thread (like Windows) + // transfer data both ways in this thread transfer_handler((void*)transfer_config); return NULL; } -// bidirectional relay using splice() for zero-copy transfer. -// data moves from kernel to kernel through a pipe, never touching userspace memory. -// This eliminates the main throughput bottleneck of copying data through userspace. +// relay data both ways using splice for zero-copy +// data goes kernel to kernel thru pipe, never hits userspace +// way faster than copying thru userspace buffers static void* transfer_handler(void *arg) { transfer_config_t *config = (transfer_config_t *)arg; @@ -1132,25 +1132,25 @@ static void* transfer_handler(void *arg) int sock2 = config->to_socket; // proxy socket free(config); - // Create pipes for splice: one per direction - // pipe_a: sock2 (proxy) → sock1 (client) = DOWNLOAD - // pipe_b: sock1 (client) → sock2 (proxy) = UPLOAD + // make 2 pipes for splice + // pipe_a: proxy to client (download) + // pipe_b: client to proxy (upload) int pipe_a[2] = {-1, -1}; int pipe_b[2] = {-1, -1}; if (pipe2(pipe_a, O_CLOEXEC | O_NONBLOCK) < 0 || pipe2(pipe_b, O_CLOEXEC | O_NONBLOCK) < 0) { - // pipe creation failed - close any that opened and fall back + // pipe failed, cleanup and fallback if (pipe_a[0] >= 0) { close(pipe_a[0]); close(pipe_a[1]); } if (pipe_b[0] >= 0) { close(pipe_b[0]); close(pipe_b[1]); } goto fallback; } - // Increase pipe capacity for throughput (default 64KB → 1MB) + // make pipes bigger for better speed (64kb to 1mb) fcntl(pipe_a[0], F_SETPIPE_SZ, 1048576); fcntl(pipe_b[0], F_SETPIPE_SZ, 1048576); - // Make sockets non-blocking for splice + // set sockets to nonblocking for splice fcntl(sock1, F_SETFL, fcntl(sock1, F_GETFL, 0) | O_NONBLOCK); fcntl(sock2, F_SETFL, fcntl(sock2, F_GETFL, 0) | O_NONBLOCK); @@ -1165,9 +1165,9 @@ static void* transfer_handler(void *arg) while (1) { - // Build poll set - use fd=-1 to exclude sockets we're done with - // CRITICAL: poll() always reports POLLHUP even if events=0, - // causing a busy-loop. Setting fd=-1 makes poll() skip the entry entirely. + // build poll set, use fd=-1 to skip closed sockets + // important: poll reports POLLHUP even with events=0 + // so use fd=-1 to actually skip it or we get busy loop fds[0].fd = (!sock1_done || pipe_a_bytes > 0 || pipe_b_bytes > 0) ? sock1 : -1; fds[1].fd = (!sock2_done || pipe_b_bytes > 0 || pipe_a_bytes > 0) ? sock2 : -1; fds[0].events = 0; @@ -1175,19 +1175,19 @@ static void* transfer_handler(void *arg) fds[0].revents = 0; fds[1].revents = 0; - // Download direction: proxy→pipe_a→client + // download: proxy to client if (!sock2_done && pipe_a_bytes == 0) fds[1].events |= POLLIN; // read from proxy if (pipe_a_bytes > 0) fds[0].events |= POLLOUT; // write to client - // Upload direction: client→pipe_b→proxy + // upload: client to proxy if (!sock1_done && pipe_b_bytes == 0) fds[0].events |= POLLIN; // read from client if (pipe_b_bytes > 0) fds[1].events |= POLLOUT; // write to proxy - // Nothing left to do + // all done if (fds[0].fd == -1 && fds[1].fd == -1) break; if (fds[0].events == 0 && fds[1].events == 0) @@ -1201,17 +1201,17 @@ static void* transfer_handler(void *arg) if (ready == 0) break; // 60s idle timeout - // Handle errors and hangups on each socket + // check for errors and hangups if (fds[0].revents & POLLERR) break; if (fds[1].revents & POLLERR) break; - // POLLHUP means peer closed - treat as EOF for reads + // pollhup means other side closed if ((fds[1].revents & POLLHUP) && !(fds[1].revents & POLLIN)) sock2_done = true; if ((fds[0].revents & POLLHUP) && !(fds[0].revents & POLLIN)) sock1_done = true; - // === DOWNLOAD: proxy (sock2) → pipe_a → client (sock1) === + // download path if (!sock2_done && pipe_a_bytes == 0 && (fds[1].revents & POLLIN)) { ssize_t n = splice(sock2, NULL, pipe_a[1], NULL, 1048576, @@ -1235,7 +1235,7 @@ static void* transfer_handler(void *arg) } } - // === UPLOAD: client (sock1) → pipe_b → proxy (sock2) === + // upload path if (!sock1_done && pipe_b_bytes == 0 && (fds[0].revents & POLLIN)) { ssize_t n = splice(sock1, NULL, pipe_b[1], NULL, 1048576, @@ -1259,8 +1259,8 @@ static void* transfer_handler(void *arg) } } - // Half-close: when one side is done and its pipe is drained, - // signal the other side with shutdown(SHUT_WR) + // half-close when one side done and pipe empty + // tell other side with shutdown if (sock2_done && pipe_a_bytes == 0 && !shut_wr_sock1) { shutdown(sock1, SHUT_WR); shut_wr_sock1 = true; @@ -1270,7 +1270,7 @@ static void* transfer_handler(void *arg) shut_wr_sock2 = true; } - // Both sides done and all pipes drained + // both sides finished and pipes empty if (sock1_done && sock2_done && pipe_a_bytes == 0 && pipe_b_bytes == 0) break; } @@ -1281,7 +1281,7 @@ static void* transfer_handler(void *arg) goto cleanup; fallback: - // Fallback: traditional recv/send relay if pipes failed + // fallback to normal recv/send if pipes didnt work { char buf[131072]; struct pollfd fds[2]; @@ -1310,7 +1310,7 @@ static void* transfer_handler(void *arg) did_work = true; } - // POLLHUP with no POLLIN data means peer closed - exit + // pollhup with no data means peer closed if (!did_work) break; } } @@ -1323,7 +1323,7 @@ static void* transfer_handler(void *arg) return NULL; } -// Proxy server - accepts connections and spawns relay threads +// proxy server accepts connections and spawns threads static void* local_proxy_server(void *arg) { (void)arg; @@ -1360,8 +1360,8 @@ static void* local_proxy_server(void *arg) return NULL; } - // Pre-create thread attributes with small stack (256KB instead of 8MB default) - // Each relay thread only needs splice() state - no 256KB userspace buffer anymore + // create thread attrs with small stack (256kb not 8mb) + // relay threads dont need big buffers anymore cuz splice pthread_attr_t thread_attr; pthread_attr_init(&thread_attr); pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); @@ -1415,20 +1415,20 @@ static void* local_proxy_server(void *arg) return NULL; } -// SOCKS5 UDP ASSOCIATE +// socks5 udp associate static int socks5_udp_associate(int s, struct sockaddr_in *relay_addr) { unsigned char buf[512]; ssize_t len; - - // Auth handshake + + // auth handshake bool use_auth = (g_proxy_username[0] != '\0'); buf[0] = SOCKS5_VERSION; buf[1] = use_auth ? 0x02 : 0x01; buf[2] = SOCKS5_AUTH_NONE; if (use_auth) buf[3] = 0x02; // username/password auth - + if (send(s, buf, use_auth ? 4 : 3, MSG_NOSIGNAL) != (use_auth ? 4 : 3)) return -1; @@ -1445,7 +1445,7 @@ static int socks5_udp_associate(int s, struct sockaddr_in *relay_addr) memcpy(buf + 2, g_proxy_username, ulen); buf[2 + ulen] = (unsigned char)plen; memcpy(buf + 3 + ulen, g_proxy_password, plen); - + if (send(s, buf, 3 + ulen + plen, MSG_NOSIGNAL) != (ssize_t)(3 + ulen + plen)) return -1; @@ -1456,7 +1456,7 @@ static int socks5_udp_associate(int s, struct sockaddr_in *relay_addr) else if (buf[1] != SOCKS5_AUTH_NONE) return -1; - // UDP ASSOCIATE request + // udp associate request buf[0] = SOCKS5_VERSION; buf[1] = SOCKS5_CMD_UDP_ASSOCIATE; buf[2] = 0x00; @@ -1471,7 +1471,7 @@ static int socks5_udp_associate(int s, struct sockaddr_in *relay_addr) if (len < 10 || buf[0] != SOCKS5_VERSION || buf[1] != 0x00) return -1; - // Extract relay address + // get relay address if (buf[3] == SOCKS5_ATYP_IPV4) { memset(relay_addr, 0, sizeof(*relay_addr)); @@ -1534,7 +1534,7 @@ static bool establish_udp_associate(void) return false; } - // RFC 1928: if server returns 0.0.0.0 as relay address, use the proxy server's IP + // rfc says if server gives 0.0.0.0 use proxy servers ip instead if (socks5_udp_relay_addr.sin_addr.s_addr == INADDR_ANY) socks5_udp_relay_addr.sin_addr.s_addr = socks5_ip; @@ -1555,7 +1555,7 @@ static bool establish_udp_associate(void) return true; } -// Tear down UDP associate state so next packet triggers reconnect +// teardown udp associate so next packet reconnects static void teardown_udp_associate(void) { udp_associate_connected = false; @@ -1599,7 +1599,7 @@ static void* udp_relay_server(void *arg) return NULL; } - // Try initial connection (non-fatal if proxy not running yet) + // try initial connect, not fatal if proxy not up yet udp_associate_connected = establish_udp_associate(); while (running) @@ -1607,17 +1607,17 @@ static void* udp_relay_server(void *arg) struct pollfd fds[3]; int nfds = 1; - // Always monitor the local relay socket for client packets + // watch local relay socket for client packets fds[0].fd = udp_relay_socket; fds[0].events = POLLIN; fds[0].revents = 0; - // Monitor SOCKS5 UDP socket for proxy responses + // watch socks5 udp socket for proxy responses fds[1].fd = (udp_associate_connected && socks5_udp_send_socket >= 0) ? socks5_udp_send_socket : -1; fds[1].events = POLLIN; fds[1].revents = 0; - // Monitor SOCKS5 TCP control socket for death detection (like Windows MSG_PEEK) + // watch socks5 tcp socket to detect if connection dies fds[2].fd = (udp_associate_connected && socks5_udp_control_socket >= 0) ? socks5_udp_control_socket : -1; fds[2].events = POLLIN; fds[2].revents = 0; @@ -1627,21 +1627,21 @@ static void* udp_relay_server(void *arg) if (ready <= 0) continue; - // Check TCP control socket health (Windows-style MSG_PEEK probe) - // If the TCP control connection dies, the UDP associate is invalid + // check if tcp control still alive + // if it dies the udp associate is dead too if (fds[2].fd >= 0 && (fds[2].revents & (POLLIN | POLLHUP | POLLERR))) { char peek_buf[1]; ssize_t peek_len = recv(socks5_udp_control_socket, peek_buf, 1, MSG_PEEK | MSG_DONTWAIT); if (peek_len == 0 || (peek_len < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) { - // TCP control connection died - proxy is gone + // tcp died, proxy is gone teardown_udp_associate(); continue; } } - // Client -> SOCKS5 proxy + // client sending to proxy if (fds[0].revents & POLLIN) { from_len = sizeof(from_addr); @@ -1650,7 +1650,7 @@ static void* udp_relay_server(void *arg) if (recv_len <= 0) continue; - // Try to establish connection if not connected (proxy may have started) + // try to connect if not connected yet if (!udp_associate_connected) { if (!establish_udp_associate()) @@ -1664,13 +1664,11 @@ static void* udp_relay_server(void *arg) if (!get_connection(client_port, &dest_ip, &dest_port)) continue; - // Buffer overflow guard: ensure data + 10-byte SOCKS5 header fits + // make sure data fits with socks5 header if (recv_len > (ssize_t)(sizeof(send_buf) - 10)) continue; - // Build SOCKS5 UDP packet: +----+------+------+----------+----------+----------+ - // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | - // +----+------+------+----------+----------+----------+ + // build socks5 udp packet header send_buf[0] = 0x00; // RSV send_buf[1] = 0x00; // RSV send_buf[2] = 0x00; // FRAG @@ -1683,21 +1681,21 @@ static void* udp_relay_server(void *arg) ssize_t sent = sendto(socks5_udp_send_socket, send_buf, 10 + recv_len, 0, (struct sockaddr *)&socks5_udp_relay_addr, sizeof(socks5_udp_relay_addr)); - // If sendto fails, proxy is dead - tear down and retry on next packet + // if send fails proxy died, teardown and retry later if (sent < 0) { teardown_udp_associate(); } } - // SOCKS5 proxy -> Client + // proxy sending back to client if (fds[1].fd >= 0 && (fds[1].revents & POLLIN)) { from_len = sizeof(from_addr); ssize_t recv_len = recvfrom(socks5_udp_send_socket, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&from_addr, &from_len); - // Handle socket errors - proxy may have died + // socket error, proxy might be dead if (recv_len < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) @@ -1707,11 +1705,11 @@ static void* udp_relay_server(void *arg) if (recv_len < 10) continue; - // Fragmented SOCKS5 UDP packets not supported + // we dont support fragmented packets if (recv_buf[2] != 0x00) continue; - // Parse SOCKS5 UDP packet + // parse socks5 udp packet if (recv_buf[3] != SOCKS5_ATYP_IPV4) continue; @@ -1721,13 +1719,13 @@ static void* udp_relay_server(void *arg) memcpy(&src_port, recv_buf + 8, 2); src_port = ntohs(src_port); - // Find the client that sent a packet to this destination - // Iterate through hash table to find matching dest_ip:dest_port + // find which client sent packet to this destination + // loop thru hash table looking for dest match pthread_rwlock_rdlock(&conn_lock); - + struct sockaddr_in client_addr; bool found_client = false; - + for (int hash = 0; hash < CONNECTION_HASH_SIZE; hash++) { CONNECTION_INFO *conn = connection_hash_table[hash]; @@ -1736,7 +1734,7 @@ static void* udp_relay_server(void *arg) if (conn->orig_dest_ip == src_ip && conn->orig_dest_port == src_port) { - // Found the connection - send response back to original client port + // found it, send response back to original client port memset(&client_addr, 0, sizeof(client_addr)); client_addr.sin_family = AF_INET; client_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); @@ -1749,19 +1747,19 @@ static void* udp_relay_server(void *arg) if (found_client) break; } - + pthread_rwlock_unlock(&conn_lock); - + if (found_client) { - // Send unwrapped UDP data back to client + // send unwrapped data back to client ssize_t data_len = recv_len - 10; sendto(udp_relay_socket, recv_buf + 10, data_len, 0, (struct sockaddr *)&client_addr, sizeof(client_addr)); } } - // Handle POLLHUP/POLLERR on the UDP send socket + // handle errors on udp send socket if (fds[1].fd >= 0 && (fds[1].revents & (POLLHUP | POLLERR))) { teardown_udp_associate(); @@ -1775,25 +1773,25 @@ static void* udp_relay_server(void *arg) return NULL; } -// nfqueue packet callback +// nfqueue callback for packets static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfad, void *data) { (void)nfmsg; (void)data; - + struct nfqnl_msg_packet_hdr *ph = nfq_get_msg_packet_hdr(nfad); if (!ph) return nfq_set_verdict(qh, 0, NF_ACCEPT, 0, NULL); uint32_t id = ntohl(ph->packet_id); - + unsigned char *payload; int payload_len = nfq_get_payload(nfad, &payload); if (payload_len < (int)sizeof(struct iphdr)) return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); struct iphdr *iph = (struct iphdr *)payload; - - // fast path - no rules configured + + // fast path when no rules if (!g_has_active_rules && g_connection_callback == NULL) return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); @@ -1813,14 +1811,14 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru src_port = ntohs(tcph->source); dest_port = ntohs(tcph->dest); - // skip packets from local relay + // skip our own packets from local relay if (src_port == g_local_relay_port) return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); if (is_connection_tracked(src_port)) return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); - // only process SYN packets for new connections + // only look at syn packets for new connections if (!(tcph->syn && !tcph->ack)) return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); @@ -1832,7 +1830,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru if (action == RULE_ACTION_PROXY && is_broadcast_or_multicast(dest_ip)) action = RULE_ACTION_DIRECT; - // log connection (skip our own process) + // log it if not from our own process if (g_traffic_logging_enabled && g_connection_callback != NULL && (tcph->syn && !tcph->ack) && pid > 0 && pid != g_current_process_id) { char process_name[MAX_PROCESS_NAME]; @@ -1861,7 +1859,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru const char* display_name = extract_filename(process_name); g_connection_callback(display_name, pid, dest_ip_str, dest_port, proxy_info); - + add_logged_connection(pid, dest_ip, dest_port, action); } } @@ -1875,7 +1873,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru { // store connection info add_connection(src_port, src_ip, dest_ip, dest_port); - + // mark packet so nat table REDIRECT rule will catch it uint32_t mark = 1; return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL); @@ -1906,7 +1904,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru if (action == RULE_ACTION_PROXY && (dest_port == 67 || dest_port == 68)) action = RULE_ACTION_DIRECT; - + // UDP proxy only works with SOCKS5, not HTTP if (action == RULE_ACTION_PROXY && g_proxy_type != PROXY_TYPE_SOCKS5) action = RULE_ACTION_DIRECT; @@ -1916,7 +1914,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru { char process_name[MAX_PROCESS_NAME]; uint32_t log_pid = (pid == 0) ? 1 : pid; // Use PID 1 for unknown processes - + if (pid > 0 && get_process_name_from_pid(pid, process_name, sizeof(process_name))) { // Got process name from PID @@ -1926,7 +1924,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru // UDP socket not found - ephemeral or timing issue snprintf(process_name, sizeof(process_name), "unknown"); } - + if (!is_connection_already_logged(log_pid, dest_ip, dest_port, action)) { char dest_ip_str[32]; @@ -1949,7 +1947,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru const char* display_name = extract_filename(process_name); g_connection_callback(display_name, log_pid, dest_ip_str, dest_port, proxy_info); - + add_logged_connection(log_pid, dest_ip, dest_port, action); } } @@ -1962,7 +1960,7 @@ static int packet_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, stru { // UDP proxy via SOCKS5 UDP ASSOCIATE add_connection(src_port, src_ip, dest_ip, dest_port); - + // Mark UDP packet for redirect to local UDP relay (port 34011) uint32_t mark = 2; // Use mark=2 for UDP (mark=1 is for TCP) return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL); @@ -2186,7 +2184,7 @@ static bool is_connection_already_logged(uint32_t pid, uint32_t dest_ip, uint16_ LOGGED_CONNECTION *logged = logged_connections; while (logged != NULL) { - if (logged->pid == pid && logged->dest_ip == dest_ip && + if (logged->pid == pid && logged->dest_ip == dest_ip && logged->dest_port == dest_port && logged->action == action) { pthread_mutex_unlock(&log_lock); @@ -2737,20 +2735,20 @@ bool ProxyBridge_Start(void) // mangle table runs before nat, so we can mark packets there int ret1 = run_iptables_cmd("-t", "mangle", "-A", "OUTPUT", "-p", "tcp", "-j", "NFQUEUE", "--queue-num", "0", NULL, NULL, NULL, NULL); int ret2 = run_iptables_cmd("-t", "mangle", "-A", "OUTPUT", "-p", "udp", "-j", "NFQUEUE", "--queue-num", "0", NULL, NULL, NULL, NULL); - + if (ret1 != 0 || ret2 != 0) { log_message("failed to add iptables rules ret1=%d ret2=%d", ret1, ret2); } else { log_message("iptables nfqueue rules added successfully"); } - + // setup nat redirect for marked packets int ret3 = run_iptables_cmd("-t", "nat", "-A", "OUTPUT", "-p", "tcp", "-m", "mark", "--mark", "1", "-j", "REDIRECT", "--to-port", "34010"); int ret4 = run_iptables_cmd("-t", "nat", "-A", "OUTPUT", "-p", "udp", "-m", "mark", "--mark", "2", "-j", "REDIRECT", "--to-port", "34011"); if (ret3 != 0 || ret4 != 0) { log_message("failed to add nat redirect rules"); } - + (void)ret3; (void)ret4; From 18a06fe2eaa51835b68ec7a28986c5f5b9313a4c Mon Sep 17 00:00:00 2001 From: Sourav Kalal Date: Sun, 8 Feb 2026 19:47:18 +0530 Subject: [PATCH 28/58] strip --- Linux/cli/main.c | 120 ++++++++++++++++++++--------------------- Linux/gui/gui_config.c | 8 +-- Linux/gui/gui_logs.c | 22 ++++---- 3 files changed, 75 insertions(+), 75 deletions(-) diff --git a/Linux/cli/main.c b/Linux/cli/main.c index 6781f14..45a540a 100644 --- a/Linux/cli/main.c +++ b/Linux/cli/main.c @@ -33,7 +33,7 @@ static void connection_callback(const char* process_name, uint32_t pid, const ch { if (verbose_level == 2 || verbose_level == 3) { - printf("[CONN] %s (PID:%u) -> %s:%u via %s\n", + printf("[CONN] %s (PID:%u) -> %s:%u via %s\n", process_name, pid, dest_ip, dest_port, proxy_info); } } @@ -43,14 +43,14 @@ static void signal_handler(int sig) if (sig == SIGSEGV || sig == SIGABRT || sig == SIGBUS) { printf("\n\n=== CLI CRASH DETECTED ===\n"); - printf("Signal: %d (%s)\n", sig, - sig == SIGSEGV ? "SEGFAULT" : + printf("Signal: %d (%s)\n", sig, + sig == SIGSEGV ? "SEGFAULT" : sig == SIGABRT ? "ABORT" : "BUS ERROR"); printf("Calling emergency cleanup...\n"); ProxyBridge_Stop(); _exit(1); } - + if (keep_running) { printf("\n\nStopping ProxyBridge...\n"); @@ -80,14 +80,14 @@ static void show_help(const char* prog) show_banner(); printf("USAGE:\n"); printf(" %s [OPTIONS]\n\n", prog); - + printf("OPTIONS:\n"); printf(" --proxy Proxy server URL with optional authentication\n"); printf(" Format: type://ip:port or type://ip:port:username:password\n"); printf(" Examples: socks5://127.0.0.1:1080\n"); printf(" http://proxy.com:8080:myuser:mypass\n"); printf(" Default: socks5://127.0.0.1:4444\n\n"); - + printf(" --rule Traffic routing rule (can be specified multiple times)\n"); printf(" Format: process:hosts:ports:protocol:action\n"); printf(" process - Process name(s): curl, cur*, *, or multiple separated by ;\n"); @@ -100,37 +100,37 @@ static void show_help(const char* prog) printf(" curl;wget:*:*:TCP:PROXY\n"); printf(" *:*:53:UDP:PROXY\n"); printf(" firefox:*:80;443:TCP:DIRECT\n\n"); - + printf(" --dns-via-proxy Route DNS queries through proxy\n"); printf(" Values: true, false, 1, 0\n"); printf(" Default: true\n\n"); - + printf(" --verbose Logging verbosity level\n"); printf(" 0 - No logs (default)\n"); printf(" 1 - Show log messages only\n"); printf(" 2 - Show connection events only\n"); printf(" 3 - Show both logs and connections\n\n"); - + printf(" --cleanup Cleanup resources (iptables, etc.) from crashed instance\n"); printf(" Use if ProxyBridge crashed without proper cleanup\n\n"); - + printf(" --help, -h Show this help message\n\n"); - + printf("EXAMPLES:\n"); printf(" # Basic usage with default proxy\n"); printf(" sudo %s --rule curl:*:*:TCP:PROXY\n\n", prog); - + printf(" # Multiple rules with custom proxy\n"); printf(" sudo %s --proxy socks5://192.168.1.10:1080 \\\n", prog); printf(" --rule curl:*:*:TCP:PROXY \\\n"); printf(" --rule wget:*:*:TCP:PROXY \\\n"); printf(" --verbose 2\n\n"); - + printf(" # Route DNS through proxy with multiple apps\n"); printf(" sudo %s --proxy socks5://127.0.0.1:1080 \\\n", prog); printf(" --rule \"curl;wget;firefox:*:*:BOTH:PROXY\" \\\n"); printf(" --dns-via-proxy true --verbose 3\n\n"); - + printf("NOTE:\n"); printf(" ProxyBridge requires root privileges to use nfqueue.\n"); printf(" Run with 'sudo' or as root user.\n\n"); @@ -142,7 +142,7 @@ static RuleProtocol parse_protocol(const char* str) for (size_t i = 0; str[i] && i < 15; i++) upper[i] = toupper(str[i]); upper[strlen(str) < 15 ? strlen(str) : 15] = '\0'; - + if (strcmp(upper, "TCP") == 0) return RULE_PROTOCOL_TCP; else if (strcmp(upper, "UDP") == 0) @@ -162,7 +162,7 @@ static RuleAction parse_action(const char* str) for (size_t i = 0; str[i] && i < 15; i++) upper[i] = toupper(str[i]); upper[strlen(str) < 15 ? strlen(str) : 15] = '\0'; - + if (strcmp(upper, "PROXY") == 0) return RULE_ACTION_PROXY; else if (strcmp(upper, "DIRECT") == 0) @@ -190,31 +190,31 @@ static bool parse_rule(const char* rule_str, ProxyRule* rule) char buffer[MAX_RULE_STR]; strncpy(buffer, rule_str, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; - + char* parts[5] = {NULL, NULL, NULL, NULL, NULL}; int part_idx = 0; char* token = strtok(buffer, ":"); - + while (token != NULL && part_idx < 5) { parts[part_idx++] = token; token = strtok(NULL, ":"); } - + if (part_idx != 5) { fprintf(stderr, "ERROR: Invalid rule format '%s'\n", rule_str); fprintf(stderr, "Expected format: process:hosts:ports:protocol:action\n"); return false; } - + default_if_empty(rule->process_name, parts[0], "*", sizeof(rule->process_name)); default_if_empty(rule->target_hosts, parts[1], "*", sizeof(rule->target_hosts)); default_if_empty(rule->target_ports, parts[2], "*", sizeof(rule->target_ports)); - + rule->protocol = parse_protocol(parts[3]); rule->action = parse_action(parts[4]); - + return true; } @@ -223,10 +223,10 @@ static bool parse_proxy_url(const char* url, ProxyType* type, char* host, uint16 char buffer[512]; strncpy(buffer, url, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; - + username[0] = '\0'; password[0] = '\0'; - + // parse type:// char* scheme_end = strstr(buffer, "://"); if (scheme_end == NULL) @@ -234,16 +234,16 @@ static bool parse_proxy_url(const char* url, ProxyType* type, char* host, uint16 fprintf(stderr, "ERROR: Invalid proxy URL format. Expected type://host:port\n"); return false; } - + *scheme_end = '\0'; char* scheme = buffer; char* rest = scheme_end + 3; - + char upper_scheme[16]; for (size_t i = 0; scheme[i] && i < 15; i++) upper_scheme[i] = toupper(scheme[i]); upper_scheme[strlen(scheme) < 15 ? strlen(scheme) : 15] = '\0'; - + if (strcmp(upper_scheme, "SOCKS5") == 0) *type = PROXY_TYPE_SOCKS5; else if (strcmp(upper_scheme, "HTTP") == 0) @@ -253,7 +253,7 @@ static bool parse_proxy_url(const char* url, ProxyType* type, char* host, uint16 fprintf(stderr, "ERROR: Invalid proxy type '%s'. Use 'socks5' or 'http'\n", scheme); return false; } - + // parse host:port[:user:pass] char* parts[4]; int num_parts = 0; @@ -263,23 +263,23 @@ static bool parse_proxy_url(const char* url, ProxyType* type, char* host, uint16 parts[num_parts++] = token; token = strtok(NULL, ":"); } - + if (num_parts < 2) { fprintf(stderr, "ERROR: Invalid proxy URL. Missing host or port\n"); return false; } - + strncpy(host, parts[0], 255); host[255] = '\0'; - + *port = atoi(parts[1]); if (*port == 0) { fprintf(stderr, "ERROR: Invalid proxy port '%s'\n", parts[1]); return false; } - + if (num_parts >= 4) { strncpy(username, parts[2], 255); @@ -287,7 +287,7 @@ static bool parse_proxy_url(const char* url, ProxyType* type, char* host, uint16 strncpy(password, parts[3], 255); password[255] = '\0'; } - + return true; } @@ -309,12 +309,12 @@ int main(int argc, char *argv[]) return 0; } } - + char proxy_url[512] = "socks5://127.0.0.1:4444"; ProxyRule rules[MAX_RULES]; int num_rules = 0; bool dns_via_proxy = true; - + // parse args for (int i = 1; i < argc; i++) { @@ -368,9 +368,9 @@ int main(int argc, char *argv[]) return 1; } } - + show_banner(); - + // need root if (!is_root()) { @@ -378,54 +378,54 @@ int main(int argc, char *argv[]) printf("Please run this application with sudo or as root.\n\n"); return 1; } - + // parse proxy config ProxyType proxy_type; char proxy_host[256]; uint16_t proxy_port; char proxy_username[256]; char proxy_password[256]; - + if (!parse_proxy_url(proxy_url, &proxy_type, proxy_host, &proxy_port, proxy_username, proxy_password)) return 1; - + // setup callbacks based on verbose // 0=nothing 1=logs 2=connections 3=both - + if (verbose_level == 1 || verbose_level == 3) ProxyBridge_SetLogCallback(log_callback); else ProxyBridge_SetLogCallback(NULL); // Explicitly disable - + if (verbose_level == 2 || verbose_level == 3) ProxyBridge_SetConnectionCallback(connection_callback); else ProxyBridge_SetConnectionCallback(NULL); // Explicitly disable - + // turn on traffic logging when needed ProxyBridge_SetTrafficLoggingEnabled(verbose_level > 0); - + // show config - printf("Proxy: %s://%s:%u\n", + printf("Proxy: %s://%s:%u\n", proxy_type == PROXY_TYPE_HTTP ? "http" : "socks5", proxy_host, proxy_port); - + if (proxy_username[0] != '\0') printf("Proxy Auth: %s:***\n", proxy_username); - + printf("DNS via Proxy: %s\n", dns_via_proxy ? "Enabled" : "Disabled"); - + // setup proxy - if (!ProxyBridge_SetProxyConfig(proxy_type, proxy_host, proxy_port, + if (!ProxyBridge_SetProxyConfig(proxy_type, proxy_host, proxy_port, proxy_username[0] ? proxy_username : "", proxy_password[0] ? proxy_password : "")) { fprintf(stderr, "ERROR: Failed to set proxy configuration\n"); return 1; } - + ProxyBridge_SetDnsViaProxy(dns_via_proxy); - + // add rules if (num_rules > 0) { @@ -436,17 +436,17 @@ int main(int argc, char *argv[]) rules[i].protocol == RULE_PROTOCOL_UDP ? "UDP" : "BOTH"; const char* action_str = rules[i].action == RULE_ACTION_PROXY ? "PROXY" : rules[i].action == RULE_ACTION_DIRECT ? "DIRECT" : "BLOCK"; - + uint32_t rule_id = ProxyBridge_AddRule( rules[i].process_name, rules[i].target_hosts, rules[i].target_ports, rules[i].protocol, rules[i].action); - + if (rule_id > 0) { - printf(" [%u] %s:%s:%s:%s -> %s\n", + printf(" [%u] %s:%s:%s:%s -> %s\n", rule_id, rules[i].process_name, rules[i].target_hosts, @@ -465,32 +465,32 @@ int main(int argc, char *argv[]) printf("\033[33mWARNING: No rules specified. No traffic will be proxied.\033[0m\n"); printf("Use --rule to add proxy rules. See --help for examples.\n"); } - + // start proxybridge if (!ProxyBridge_Start()) { fprintf(stderr, "ERROR: Failed to start ProxyBridge\n"); return 1; } - + keep_running = true; printf("\nProxyBridge started. Press Ctrl+C to stop...\n\n"); - + signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGSEGV, signal_handler); // Catch segfault signal(SIGABRT, signal_handler); // Catch abort signal(SIGBUS, signal_handler); // Catch bus error - + // main loop while (keep_running) { sleep(1); } - + // cleanup ProxyBridge_Stop(); printf("ProxyBridge stopped.\n"); - + return 0; } diff --git a/Linux/gui/gui_config.c b/Linux/gui/gui_config.c index 0abf955..4030967 100644 --- a/Linux/gui/gui_config.c +++ b/Linux/gui/gui_config.c @@ -59,7 +59,7 @@ void load_config() { while (fgets(line, sizeof(line), f)) { // trim newline line[strcspn(line, "\r\n")] = 0; - + if (strlen(line) == 0 || line[0] == '#') continue; if (strcmp(line, "[SETTINGS]") == 0) { section = 1; continue; } @@ -69,7 +69,7 @@ void load_config() { char *val = strchr(line, '='); if (!val) continue; *val = 0; val++; - + if (strcmp(line, "ip") == 0) strncpy(g_proxy_ip, val, sizeof(g_proxy_ip) - 1); else if (strcmp(line, "port") == 0) g_proxy_port = atoi(val); else if (strcmp(line, "type") == 0) g_proxy_type = atoi(val); @@ -84,7 +84,7 @@ void load_config() { char *p = line; char *token; int idx = 0; - + while ((token = strsep(&p, "|")) != NULL) { switch(idx) { case 0: rule->id = atoi(token); break; @@ -97,7 +97,7 @@ void load_config() { } idx++; } - + if (idx >= 4) { g_rules_list = g_list_append(g_rules_list, rule); } else { diff --git a/Linux/gui/gui_logs.c b/Linux/gui/gui_logs.c index 3ff4c68..8fc8d2f 100644 --- a/Linux/gui/gui_logs.c +++ b/Linux/gui/gui_logs.c @@ -3,7 +3,7 @@ // filter logs based on search static void filter_text_view(GtkTextBuffer *buffer, const char *text) { if (!buffer) return; - + GtkTextIter start, end; gtk_text_buffer_get_bounds(buffer, &start, &end); gtk_text_buffer_remove_tag_by_name(buffer, "hidden", &start, &end); @@ -15,23 +15,23 @@ static void filter_text_view(GtkTextBuffer *buffer, const char *text) { GtkTextIter line_end = line_start; if (!gtk_text_iter_ends_line(&line_end)) gtk_text_iter_forward_to_line_end(&line_end); - + char *line_text = gtk_text_buffer_get_text(buffer, &line_start, &line_end, FALSE); - + // search case insensitive char *lower_line = g_utf8_strdown(line_text, -1); char *lower_search = g_utf8_strdown(text, -1); - if (!strstr(lower_line, lower_search)) { - GtkTextIter next_line = line_end; + if (!strstr(lower_line, lower_search)) { + GtkTextIter next_line = line_end; gtk_text_iter_forward_char(&next_line); // include newline gtk_text_buffer_apply_tag_by_name(buffer, "hidden", &line_start, &next_line); } - + g_free(lower_line); g_free(lower_search); g_free(line_text); - + gtk_text_iter_forward_line(&line_start); } } @@ -71,7 +71,7 @@ static gboolean update_log_gui(gpointer user_data) { GtkTextIter end; gtk_text_buffer_get_end_iter(log_buffer, &end); - + char *time_str = get_current_time_str(); char full_msg[1200]; snprintf(full_msg, sizeof(full_msg), "%s %s\n", time_str, data->message); @@ -94,11 +94,11 @@ static gboolean update_connection_gui_append(gpointer user_data) { if (conn_buffer) { GtkTextIter end; gtk_text_buffer_get_end_iter(conn_buffer, &end); - + char line_buffer[1024]; - snprintf(line_buffer, sizeof(line_buffer), "%s %s (PID:%u) -> %s:%u via %s\n", + snprintf(line_buffer, sizeof(line_buffer), "%s %s (PID:%u) -> %s:%u via %s\n", data->timestamp, data->process_name, data->pid, data->dest_ip, data->dest_port, data->proxy_info); - + gtk_text_buffer_insert(conn_buffer, &end, line_buffer, -1); trim_buffer_lines(conn_buffer, 100); From b4d11e02502ddae50ff9606e539b5dc9f36b3d6a Mon Sep 17 00:00:00 2001 From: Sourav Kalal Date: Sun, 8 Feb 2026 22:53:26 +0530 Subject: [PATCH 29/58] Fixed batch error cause network issues --- Windows/src/ProxyBridge.c | 47 ++++++++++++--------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/Windows/src/ProxyBridge.c b/Windows/src/ProxyBridge.c index d4b0a9d..1bf6899 100644 --- a/Windows/src/ProxyBridge.c +++ b/Windows/src/ProxyBridge.c @@ -12,15 +12,14 @@ #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "ws2_32.lib") -#define MAXBUF 1500 // Optimized for typical MTU (WinDivert performance recommendation) -#define PACKET_BATCH_SIZE 64 // Batch mode: process multiple packets per context switch +#define MAXBUF 0xFFFF #define LOCAL_PROXY_PORT 34010 #define LOCAL_UDP_RELAY_PORT 34011 // its running UDP port still make sure to not run on same port as TCP, opening same port and tcp and udp cause issue and handling port at relay server response injection #define MAX_PROCESS_NAME 256 #define VERSION "3.1.0" #define PID_CACHE_SIZE 1024 #define PID_CACHE_TTL_MS 1000 -#define NUM_PACKET_THREADS 4 // Multi-threading for parallel packet processing +#define NUM_PACKET_THREADS 4 #define CONNECTION_HASH_SIZE 256 #define SOCKS5_BUFFER_SIZE 1024 #define HTTP_BUFFER_SIZE 1024 @@ -106,7 +105,6 @@ static BOOL running = FALSE; static DWORD g_current_process_id = 0; static BOOL g_traffic_logging_enabled = TRUE; -static BOOL g_ipv6_warning_logged = FALSE; static char g_proxy_host[256] = ""; // Can be IP address or hostname static UINT16 g_proxy_port = 0; @@ -267,23 +265,11 @@ static DWORD WINAPI packet_processor(LPVOID arg) continue; } - PWINDIVERT_IPV6HDR ipv6_header = NULL; - WinDivertHelperParsePacket(packet, packet_len, &ip_header, &ipv6_header, NULL, + WinDivertHelperParsePacket(packet, packet_len, &ip_header, NULL, NULL, NULL, NULL, &tcp_header, &udp_header, NULL, NULL, NULL, NULL); if (ip_header == NULL) - { - if (ipv6_header != NULL) - { - if (!g_ipv6_warning_logged) - { - log_message("IPv6 traffic detected but not supported. IPv6 connections will not be proxied."); - g_ipv6_warning_logged = TRUE; - } - WinDivertSend(windivert_handle, packet, packet_len, NULL, &addr); - } continue; - } if (udp_header != NULL && tcp_header == NULL) { @@ -318,7 +304,7 @@ static DWORD WINAPI packet_processor(LPVOID arg) UINT32 dest_ip = ip_header->DstAddr; UINT16 dest_port = ntohs(udp_header->DstPort); - // if no rule configured all connection direct with no checks avoid unwanted memory and processing which could delay + // if no rule configuree all connection direct with no checks avoid unwanted memory and pocessing whcich could delay if (!g_has_active_rules && g_connection_callback == NULL) { // No rules and no logging - pass through immediately (no checksum needed for unmodified packets) @@ -461,7 +447,7 @@ static DWORD WINAPI packet_processor(LPVOID arg) UINT32 orig_dest_ip = ip_header->DstAddr; UINT16 orig_dest_port = ntohs(tcp_header->DstPort); - // avoid rule process and packet process if no rules + // avoid rule pocess and packet process if no rules if (!g_has_active_rules && g_connection_callback == NULL) { WinDivertSend(windivert_handle, packet, packet_len, NULL, &addr); @@ -532,14 +518,14 @@ static DWORD WINAPI packet_processor(LPVOID arg) continue; } else if (action == RULE_ACTION_PROXY) - { - add_connection(src_port, src_ip, orig_dest_ip, orig_dest_port); + { + add_connection(src_port, src_ip, orig_dest_ip, orig_dest_port); - UINT32 temp_addr = ip_header->DstAddr; - tcp_header->DstPort = htons(g_local_relay_port); - ip_header->DstAddr = ip_header->SrcAddr; - ip_header->SrcAddr = temp_addr; - addr.Outbound = FALSE; + UINT32 temp_addr = ip_header->DstAddr; + tcp_header->DstPort = htons(g_local_relay_port); + ip_header->DstAddr = ip_header->SrcAddr; + ip_header->SrcAddr = temp_addr; + addr.Outbound = FALSE; } } } @@ -1482,10 +1468,6 @@ static BOOL establish_udp_associate(void) return FALSE; } - // RFC 1928: if server returns 0.0.0.0 as relay address, use the proxy server's IP - if (socks5_udp_relay_addr.sin_addr.s_addr == INADDR_ANY) - socks5_udp_relay_addr.sin_addr.s_addr = socks5_ip; - socks5_udp_socket = tcp_sock; // ccreate UDP socket for sending to SOCKS5 proxy @@ -2756,9 +2738,8 @@ PROXYBRIDGE_API BOOL ProxyBridge_Start(void) return FALSE; } - // Performance optimization: larger queue to prevent packet drops under heavy load (>= 1Gbps) - WinDivertSetParam(windivert_handle, WINDIVERT_PARAM_QUEUE_LENGTH, (UINT64)16384); // Doubled queue length - WinDivertSetParam(windivert_handle, WINDIVERT_PARAM_QUEUE_TIME, (UINT64)2000); // 2 seconds max queue time + WinDivertSetParam(windivert_handle, WINDIVERT_PARAM_QUEUE_LENGTH, 8192); + WinDivertSetParam(windivert_handle, WINDIVERT_PARAM_QUEUE_TIME, 8); // 8ms for low latency for (int i = 0; i < NUM_PACKET_THREADS; i++) { From 00f8a4a68e7579210875c5db4a80df250e2732f7 Mon Sep 17 00:00:00 2001 From: Sourav Kalal Date: Sun, 8 Feb 2026 22:57:20 +0530 Subject: [PATCH 30/58] readded #62 ipv6 pass --- Windows/src/ProxyBridge.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Windows/src/ProxyBridge.c b/Windows/src/ProxyBridge.c index 1bf6899..8fadece 100644 --- a/Windows/src/ProxyBridge.c +++ b/Windows/src/ProxyBridge.c @@ -265,11 +265,19 @@ static DWORD WINAPI packet_processor(LPVOID arg) continue; } - WinDivertHelperParsePacket(packet, packet_len, &ip_header, NULL, NULL, + PWINDIVERT_IPV6HDR ipv6_header = NULL; + WinDivertHelperParsePacket(packet, packet_len, &ip_header, &ipv6_header, NULL, NULL, NULL, &tcp_header, &udp_header, NULL, NULL, NULL, NULL); if (ip_header == NULL) + { + // IPv6 traffic pass directly without proxying + if (ipv6_header != NULL) + { + WinDivertSend(windivert_handle, packet, packet_len, NULL, &addr); + } continue; + } if (udp_header != NULL && tcp_header == NULL) { From 47063b9cc344f76e4053b380a0d0b2afffa72a73 Mon Sep 17 00:00:00 2001 From: Sourav Kalal Date: Fri, 13 Feb 2026 14:18:59 +0530 Subject: [PATCH 31/58] #60 - readded fixed rule priority --- Windows/gui/Interop/ProxyBridgeNative.cs | 7 ++ Windows/gui/Services/ProxyBridgeService.cs | 10 +++ Windows/gui/ViewModels/ProxyRulesViewModel.cs | 8 ++ Windows/gui/Views/ProxyRulesWindow.axaml | 4 +- Windows/gui/Views/ProxyRulesWindow.axaml.cs | 73 ++++++++++++++++ Windows/src/ProxyBridge.c | 86 +++++++++++++++++++ Windows/src/ProxyBridge.h | 2 + 7 files changed, 189 insertions(+), 1 deletion(-) diff --git a/Windows/gui/Interop/ProxyBridgeNative.cs b/Windows/gui/Interop/ProxyBridgeNative.cs index 4c1c514..4451f82 100644 --- a/Windows/gui/Interop/ProxyBridgeNative.cs +++ b/Windows/gui/Interop/ProxyBridgeNative.cs @@ -83,6 +83,13 @@ public static extern bool ProxyBridge_EditRule( RuleProtocol protocol, RuleAction action); + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + public static extern uint ProxyBridge_GetRulePosition(uint ruleId); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ProxyBridge_MoveRuleToPosition(uint ruleId, uint newPosition); + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ProxyBridge_SetProxyConfig( diff --git a/Windows/gui/Services/ProxyBridgeService.cs b/Windows/gui/Services/ProxyBridgeService.cs index 6ddd5cc..be93ae3 100644 --- a/Windows/gui/Services/ProxyBridgeService.cs +++ b/Windows/gui/Services/ProxyBridgeService.cs @@ -113,6 +113,16 @@ public bool EditRule(uint ruleId, string processName, string targetHosts, string return ProxyBridgeNative.ProxyBridge_EditRule(ruleId, processName, targetHosts, targetPorts, ruleProtocol, ruleAction); } + public uint GetRulePosition(uint ruleId) + { + return ProxyBridgeNative.ProxyBridge_GetRulePosition(ruleId); + } + + public bool MoveRuleToPosition(uint ruleId, uint newPosition) + { + return ProxyBridgeNative.ProxyBridge_MoveRuleToPosition(ruleId, newPosition); + } + public void SetDnsViaProxy(bool enable) { ProxyBridgeNative.ProxyBridge_SetDnsViaProxy(enable); diff --git a/Windows/gui/ViewModels/ProxyRulesViewModel.cs b/Windows/gui/ViewModels/ProxyRulesViewModel.cs index 61ebb77..811b10e 100644 --- a/Windows/gui/ViewModels/ProxyRulesViewModel.cs +++ b/Windows/gui/ViewModels/ProxyRulesViewModel.cs @@ -96,6 +96,14 @@ public void SetWindow(Window window) _window = window; } + public bool MoveRuleToPosition(uint ruleId, uint newPosition) + { + if (_proxyService == null) + return false; + + return _proxyService.MoveRuleToPosition(ruleId, newPosition); + } + private void ResetRuleForm() { NewProcessName = "*"; diff --git a/Windows/gui/Views/ProxyRulesWindow.axaml b/Windows/gui/Views/ProxyRulesWindow.axaml index 620a051..7da1fc1 100644 --- a/Windows/gui/Views/ProxyRulesWindow.axaml +++ b/Windows/gui/Views/ProxyRulesWindow.axaml @@ -151,7 +151,9 @@ + Padding="20,16" + PointerPressed="Rule_PointerPressed" + DragDrop.AllowDrop="True"> ("RulesItemsControl") is ItemsControl itemsControl) + { + itemsControl.AddHandler(DragDrop.DropEvent, Rules_Drop); + itemsControl.AddHandler(DragDrop.DragOverEvent, Rules_DragOver); + } } private void ProxyRulesWindow_DataContextChanged(object? sender, EventArgs e) @@ -151,4 +158,70 @@ item.Tag is string tag && vm.NewProtocol = tag; } } + + private async void Rule_PointerPressed(object? sender, PointerPressedEventArgs e) + { + if (sender is not Border border || border.DataContext is not ProxyRule rule) + return; + + var dragData = new DataObject(); + dragData.Set("DraggedRule", rule); + + var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Move); + + if (result == DragDropEffects.Move && DataContext is ProxyRulesViewModel vm) + { + // refsh indices after drag completes + for (int i = 0; i < vm.ProxyRules.Count; i++) + { + vm.ProxyRules[i].Index = i + 1; + } + } + } + + private void Rules_DragOver(object? sender, DragEventArgs e) + { + e.DragEffects = DragDropEffects.Move; + } + + private void Rules_Drop(object? sender, DragEventArgs e) + { + if (DataContext is not ProxyRulesViewModel vm) + return; + + if (e.Data.Get("DraggedRule") is not ProxyRule draggedRule) + return; + + if (e.Source is Control control) + { + var current = control; + while (current != null && current is not Border) + { + current = current.Parent as Control; + } + + if (current is Border border && border.DataContext is ProxyRule targetRule) + { + if (draggedRule.RuleId == targetRule.RuleId) + return; + + int draggedIndex = vm.ProxyRules.IndexOf(draggedRule); + int targetIndex = vm.ProxyRules.IndexOf(targetRule); + + if (draggedIndex == -1 || targetIndex == -1 || draggedIndex == targetIndex) + return; + + uint newPosition = (uint)(targetIndex + 1); + if (vm.MoveRuleToPosition(draggedRule.RuleId, newPosition)) + { + vm.ProxyRules.Move(draggedIndex, targetIndex); + + for (int i = 0; i < vm.ProxyRules.Count; i++) + { + vm.ProxyRules[i].Index = i + 1; + } + } + } + } + } } \ No newline at end of file diff --git a/Windows/src/ProxyBridge.c b/Windows/src/ProxyBridge.c index 8fadece..c44434e 100644 --- a/Windows/src/ProxyBridge.c +++ b/Windows/src/ProxyBridge.c @@ -2393,6 +2393,92 @@ PROXYBRIDGE_API BOOL ProxyBridge_EditRule(UINT32 rule_id, const char* process_na return FALSE; } +PROXYBRIDGE_API UINT32 ProxyBridge_GetRulePosition(UINT32 rule_id) +{ + if (rule_id == 0) + return 0; + + UINT32 position = 1; + PROCESS_RULE *rule = rules_list; + while (rule != NULL) + { + if (rule->rule_id == rule_id) + return position; + position++; + rule = rule->next; + } + return 0; +} + +PROXYBRIDGE_API BOOL ProxyBridge_MoveRuleToPosition(UINT32 rule_id, UINT32 new_position) +{ + if (rule_id == 0 || new_position == 0) + return FALSE; + + // first rule and remove it from current position + PROCESS_RULE *rule = rules_list; + PROCESS_RULE *prev = NULL; + + while (rule != NULL) + { + if (rule->rule_id == rule_id) + break; + prev = rule; + rule = rule->next; + } + + if (rule == NULL) + return FALSE; + + // Remove from current position + if (prev == NULL) + { + rules_list = rule->next; + } + else + { + prev->next = rule->next; + } + + // Insert at new position + if (new_position == 1) + { + // Insert at head + rule->next = rules_list; + rules_list = rule; + } + else + { + // taken from stackflow + PROCESS_RULE *current = rules_list; + UINT32 pos = 1; + + while (current != NULL && pos < new_position - 1) + { + current = current->next; + pos++; + } + + if (current == NULL) + { + // position is beyond list end we can append to tail + current = rules_list; + while (current->next != NULL) + current = current->next; + current->next = rule; + rule->next = NULL; + } + else + { + rule->next = current->next; + current->next = rule; + } + } + + log_message("Moved rule ID %u to position %u", rule_id, new_position); + return TRUE; +} + PROXYBRIDGE_API BOOL ProxyBridge_SetProxyConfig(ProxyType type, const char* proxy_ip, UINT16 proxy_port, const char* username, const char* password) { if (proxy_ip == NULL || proxy_ip[0] == '\0' || proxy_port == 0) diff --git a/Windows/src/ProxyBridge.h b/Windows/src/ProxyBridge.h index 6f7f1c1..ad6cd31 100644 --- a/Windows/src/ProxyBridge.h +++ b/Windows/src/ProxyBridge.h @@ -38,6 +38,8 @@ PROXYBRIDGE_API BOOL ProxyBridge_EnableRule(UINT32 rule_id); PROXYBRIDGE_API BOOL ProxyBridge_DisableRule(UINT32 rule_id); PROXYBRIDGE_API BOOL ProxyBridge_DeleteRule(UINT32 rule_id); PROXYBRIDGE_API BOOL ProxyBridge_EditRule(UINT32 rule_id, const char* process_name, const char* target_hosts, const char* target_ports, RuleProtocol protocol, RuleAction action); +PROXYBRIDGE_API BOOL ProxyBridge_MoveRuleToPosition(UINT32 rule_id, UINT32 new_position); // Move rule to specific position (1=first, 2=second, etc) +PROXYBRIDGE_API UINT32 ProxyBridge_GetRulePosition(UINT32 rule_id); // Get current position of rule in list (1-based) PROXYBRIDGE_API BOOL ProxyBridge_SetProxyConfig(ProxyType type, const char* proxy_ip, UINT16 proxy_port, const char* username, const char* password); // proxy_ip can be IP address or hostname PROXYBRIDGE_API void ProxyBridge_SetDnsViaProxy(BOOL enable); PROXYBRIDGE_API void ProxyBridge_SetLogCallback(LogCallback callback); From 3ad55abcf944353590d3033b450f81b9d772c473 Mon Sep 17 00:00:00 2001 From: Sourav Kalal Date: Fri, 13 Feb 2026 17:28:10 +0530 Subject: [PATCH 32/58] #60 added delete selection proxy rule --- Windows/gui/ViewModels/ProxyRulesViewModel.cs | 28 +++++++++++++++++++ Windows/gui/Views/ProxyRulesWindow.axaml | 9 ++++++ 2 files changed, 37 insertions(+) diff --git a/Windows/gui/ViewModels/ProxyRulesViewModel.cs b/Windows/gui/ViewModels/ProxyRulesViewModel.cs index 811b10e..6f8f6bd 100644 --- a/Windows/gui/ViewModels/ProxyRulesViewModel.cs +++ b/Windows/gui/ViewModels/ProxyRulesViewModel.cs @@ -87,6 +87,7 @@ public string ProcessNameError public ICommand ToggleSelectAllCommand { get; } public ICommand ExportRulesCommand { get; } public ICommand ImportRulesCommand { get; } + public ICommand DeleteSelectedRulesCommand { get; } public bool HasSelectedRules => ProxyRules.Any(r => r.IsSelected); public bool AllRulesSelected => ProxyRules.Any() && ProxyRules.All(r => r.IsSelected); @@ -314,6 +315,33 @@ public ProxyRulesViewModel(ObservableCollection proxyRules, Action + { + var selectedRules = ProxyRules.Where(r => r.IsSelected).ToList(); + if (selectedRules.Count == 0) + return; + + var confirmMsg = selectedRules.Count == 1 + ? $"Delete 1 selected rule?" + : $"Delete {selectedRules.Count} selected rules?"; + + var confirmed = await ShowConfirmDialogAsync("Delete Selected Rules", confirmMsg); + if (!confirmed) + return; + + foreach (var rule in selectedRules) + { + if (_proxyService != null && _proxyService.DeleteRule(rule.RuleId)) + { + ProxyRules.Remove(rule); + } + } + + _onConfigChanged?.Invoke(); + OnPropertyChanged(nameof(HasSelectedRules)); + OnPropertyChanged(nameof(AllRulesSelected)); + }); } private async System.Threading.Tasks.Task ShowConfirmDialogAsync(string title, string message) diff --git a/Windows/gui/Views/ProxyRulesWindow.axaml b/Windows/gui/Views/ProxyRulesWindow.axaml index 7da1fc1..b54d75c 100644 --- a/Windows/gui/Views/ProxyRulesWindow.axaml +++ b/Windows/gui/Views/ProxyRulesWindow.axaml @@ -48,6 +48,15 @@ +