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 @@
+
+
+
+
+
+
Date: Fri, 13 Feb 2026 17:28:21 +0530
Subject: [PATCH 33/58] strip
---
Windows/gui/ViewModels/ProxyRulesViewModel.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Windows/gui/ViewModels/ProxyRulesViewModel.cs b/Windows/gui/ViewModels/ProxyRulesViewModel.cs
index 6f8f6bd..7556f14 100644
--- a/Windows/gui/ViewModels/ProxyRulesViewModel.cs
+++ b/Windows/gui/ViewModels/ProxyRulesViewModel.cs
@@ -322,7 +322,7 @@ public ProxyRulesViewModel(ObservableCollection proxyRules, Action
Date: Fri, 13 Feb 2026 19:28:05 +0530
Subject: [PATCH 34/58] #71 - removed domain name
---
Windows/gui/Resources/Resources.resx | 2 +-
Windows/gui/Resources/Resources.zh.resx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Windows/gui/Resources/Resources.resx b/Windows/gui/Resources/Resources.resx
index 5fa3e8e..0b64c18 100644
--- a/Windows/gui/Resources/Resources.resx
+++ b/Windows/gui/Resources/Resources.resx
@@ -228,7 +228,7 @@
Example: iexplore.exe; "C:\some app.exe"; fire*.exe; *.bin
- Example: 127.0.0.1; *.example.com; 192.168.1.*; 10.1.0.0-10.5.255.255
+ Example: 127.0.0.1; 192.168.1.*; 10.1.0.0-10.5.255.255
Example: 80; 8000-9000; 3128
diff --git a/Windows/gui/Resources/Resources.zh.resx b/Windows/gui/Resources/Resources.zh.resx
index 173fc3c..981fd88 100644
--- a/Windows/gui/Resources/Resources.zh.resx
+++ b/Windows/gui/Resources/Resources.zh.resx
@@ -229,7 +229,7 @@
示例: iexplore.exe; "C:\某个应用.exe"; fire*.exe; *.bin
- 示例: 127.0.0.1; *.example.com; 192.168.1.*; 10.1.0.0-10.5.255.255
+ 示例: 127.0.0.1; 192.168.1.*; 10.1.0.0-10.5.255.255
示例: 80; 8000-9000; 3128
From abfae9b8be747936decc7b8de02eda4b96fa4adf Mon Sep 17 00:00:00 2001
From: Sourav Kalal
Date: Fri, 13 Feb 2026 20:25:36 +0530
Subject: [PATCH 35/58] #70 - added localhost proxy support
---
Windows/src/ProxyBridge.c | 89 +++++++++++++++++++++++++++++----------
1 file changed, 66 insertions(+), 23 deletions(-)
diff --git a/Windows/src/ProxyBridge.c b/Windows/src/ProxyBridge.c
index c44434e..8bcfdcb 100644
--- a/Windows/src/ProxyBridge.c
+++ b/Windows/src/ProxyBridge.c
@@ -385,15 +385,21 @@ static DWORD WINAPI packet_processor(LPVOID arg)
{
add_connection(src_port, src_ip, dest_ip, dest_port);
- // dest to rely udp
+ // redirect to UDP relay server at 127.0.0.1:34011
udp_header->DstPort = htons(LOCAL_UDP_RELAY_PORT);
-
- // Set destination to localhost (127.0.0.1)
ip_header->DstAddr = htonl(INADDR_LOOPBACK);
- // Keep source IP unchanged
- addr.Outbound = FALSE;
+ // check if source is localhos
+ BYTE src_first_octet = (ntohl(ip_header->SrcAddr) >> 24) & 0xFF;
+ BOOL src_is_loopback = (src_first_octet == 127);
+ if (!src_is_loopback)
+ {
+ // for non loopback source: mark as inbound
+ addr.Outbound = FALSE;
+ }
+ // for loopback we need keep as outbound (127.x.x.x -> 127.0.0.1)
+ // for a fucking stupid reason i missed this part for 6 months
}
}
}
@@ -427,10 +433,18 @@ static DWORD WINAPI packet_processor(LPVOID arg)
if (get_connection(dst_port, &orig_dest_ip, &orig_dest_port))
tcp_header->SrcPort = htons(orig_dest_port);
- UINT32 temp_addr = ip_header->DstAddr;
- ip_header->DstAddr = ip_header->SrcAddr;
- ip_header->SrcAddr = temp_addr;
- addr.Outbound = FALSE;
+ BYTE src_first = (ntohl(ip_header->SrcAddr) >> 24) & 0xFF;
+ BYTE dst_first = (ntohl(ip_header->DstAddr) >> 24) & 0xFF;
+ BOOL is_loopback = (src_first == 127 && dst_first == 127);
+
+ if (!is_loopback)
+ {
+ UINT32 temp_addr = ip_header->DstAddr;
+ ip_header->DstAddr = ip_header->SrcAddr;
+ ip_header->SrcAddr = temp_addr;
+ addr.Outbound = FALSE;
+ }
+
if (tcp_header->Fin || tcp_header->Rst)
remove_connection(dst_port);
@@ -442,11 +456,20 @@ static DWORD WINAPI packet_processor(LPVOID arg)
if (tcp_header->Fin || tcp_header->Rst)
remove_connection(src_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;
+
+ BYTE src_first = (ntohl(ip_header->SrcAddr) >> 24) & 0xFF;
+ BYTE dst_first = (ntohl(ip_header->DstAddr) >> 24) & 0xFF;
+ BOOL is_loopback = (src_first == 127 && dst_first == 127);
+
+ if (!is_loopback)
+ {
+ UINT32 temp_addr = ip_header->DstAddr;
+ ip_header->DstAddr = ip_header->SrcAddr;
+ ip_header->SrcAddr = temp_addr;
+ addr.Outbound = FALSE;
+ }
+
}
else
{
@@ -529,11 +552,29 @@ static DWORD WINAPI packet_processor(LPVOID arg)
{
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;
+
+ // check if this is localhost -> localhost traffic
+ BYTE src_first_octet = (ntohl(ip_header->SrcAddr) >> 24) & 0xFF;
+ BYTE dst_first_octet = (ntohl(ip_header->DstAddr) >> 24) & 0xFF;
+ BOOL is_loopback_to_loopback = (src_first_octet == 127 && dst_first_octet == 127);
+
+ if (is_loopback_to_loopback)
+ {
+ // for localhost -> localhost just change port, keep as outbound
+ // dont swap IPs Windows loopback routing needs both to stay 127.x.x.x
+ log_message("[PACKET] Loopback redirect: 127.x.x.x:%d -> 127.x.x.x:%d (relay port %d)",
+ ntohs(tcp_header->SrcPort), orig_dest_port, g_local_relay_port);
+ // addr.Outbound stays TRUE
+ }
+ else
+ {
+ // for normal traffic: swap IPs and mark as inbound (standard relay behavior)
+ UINT32 temp_addr = ip_header->DstAddr;
+ ip_header->DstAddr = ip_header->SrcAddr;
+ ip_header->SrcAddr = temp_addr;
+ addr.Outbound = FALSE;
+ }
}
}
}
@@ -985,13 +1026,13 @@ static BOOL match_process_list(const char *process_list, const char *process_nam
static BOOL is_broadcast_or_multicast(UINT32 ip)
{
- // Localhost: 127.0.0.0/8 (127.x.x.x)
+ // note: Localhost (127.x.x.x) is now supported for proxying
+ // This allows intercepting localhost connections for MITM scenarios
+
BYTE first_octet = (ip >> 0) & 0xFF;
- if (first_octet == 127)
- return TRUE;
+ BYTE second_octet = (ip >> 8) & 0xFF;
// APIPA (Link-Local): 169.254.0.0/16 (169.254.x.x)
- BYTE second_octet = (ip >> 8) & 0xFF;
if (first_octet == 169 && second_octet == 254)
return TRUE;
@@ -1888,7 +1929,7 @@ static DWORD WINAPI connection_handler(LPVOID arg)
if (connect(socks_sock, (struct sockaddr *)&socks_addr, sizeof(socks_addr)) == SOCKET_ERROR)
{
- log_message("Failed to connect to proxy (%d)", WSAGetLastError());
+ log_message("[RELAY] Failed to connect to proxy (%d)", WSAGetLastError());
closesocket(client_sock);
closesocket(socks_sock);
return 0;
@@ -2818,9 +2859,11 @@ PROXYBRIDGE_API BOOL ProxyBridge_Start(void)
Sleep(500);
snprintf(filter, sizeof(filter),
- "(tcp and (outbound or (tcp.DstPort == %d or tcp.SrcPort == %d))) or (udp and (outbound or (udp.DstPort == %d or udp.SrcPort == %d)))",
+ "(tcp and (outbound or loopback or (tcp.DstPort == %d or tcp.SrcPort == %d))) or (udp and (outbound or loopback or (udp.DstPort == %d or udp.SrcPort == %d)))",
g_local_relay_port, g_local_relay_port, LOCAL_UDP_RELAY_PORT, LOCAL_UDP_RELAY_PORT);
+ // Note: Added 'loopback' to filter to capture localhost (127.x.x.x) traffic
+ // This enables proxying local connections for MITM scenarios
windivert_handle = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, priority, 0);
if (windivert_handle == INVALID_HANDLE_VALUE)
{
From b7ea32d4d9442b6a88397118afb432356124ddde Mon Sep 17 00:00:00 2001
From: Sourav Kalal
Date: Fri, 13 Feb 2026 20:41:53 +0530
Subject: [PATCH 36/58] added localhost proxy support on/off #70
---
Windows/src/ProxyBridge.c | 16 ++++++++++++++++
Windows/src/ProxyBridge.h | 1 +
2 files changed, 17 insertions(+)
diff --git a/Windows/src/ProxyBridge.c b/Windows/src/ProxyBridge.c
index 8bcfdcb..4e3a836 100644
--- a/Windows/src/ProxyBridge.c
+++ b/Windows/src/ProxyBridge.c
@@ -113,6 +113,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 BOOL g_localhost_via_proxy = FALSE; // default disabled for security - most proxy server block localhost for ssrf and also many app might not work if localhost trafic goes to remote server if proxy server is on diffrent machine
static LogCallback g_log_callback = NULL;
static ConnectionCallback g_connection_callback = NULL;
@@ -328,6 +329,11 @@ static DWORD WINAPI packet_processor(LPVOID arg)
else
action = check_process_rule(src_ip, src_port, dest_ip, dest_port, TRUE, &pid);
+ // override PROXY to DIRECT if localhost proxy is disabled and destination is localhost
+ BYTE dest_first_octet = (dest_ip >> 0) & 0xFF;
+ if (action == RULE_ACTION_PROXY && !g_localhost_via_proxy && dest_first_octet == 127)
+ action = RULE_ACTION_DIRECT;
+
// Override PROXY to DIRECT for critical IPs and ports
if (action == RULE_ACTION_PROXY && is_broadcast_or_multicast(dest_ip))
action = RULE_ACTION_DIRECT;
@@ -493,6 +499,10 @@ static DWORD WINAPI packet_processor(LPVOID arg)
else
action = check_process_rule(src_ip, src_port, orig_dest_ip, orig_dest_port, FALSE, &pid);
+ BYTE orig_dest_first_octet = (orig_dest_ip >> 0) & 0xFF;
+ if (action == RULE_ACTION_PROXY && !g_localhost_via_proxy && orig_dest_first_octet == 127)
+ action = RULE_ACTION_DIRECT;
+
// Override PROXY to DIRECT for criticl ips
if (action == RULE_ACTION_PROXY && is_broadcast_or_multicast(orig_dest_ip))
action = RULE_ACTION_DIRECT;
@@ -2561,6 +2571,12 @@ PROXYBRIDGE_API void ProxyBridge_SetDnsViaProxy(BOOL enable)
log_message("DNS routing: %s", enable ? "via proxy" : "direct");
}
+PROXYBRIDGE_API void ProxyBridge_SetLocalhostViaProxy(BOOL enable)
+{
+ g_localhost_via_proxy = enable;
+ log_message("Localhost routing: %s (most proxies block localhost for SSRF prevention)", enable ? "via proxy" : "direct");
+}
+
PROXYBRIDGE_API void ProxyBridge_SetLogCallback(LogCallback callback)
{
g_log_callback = callback;
diff --git a/Windows/src/ProxyBridge.h b/Windows/src/ProxyBridge.h
index ad6cd31..f728b76 100644
--- a/Windows/src/ProxyBridge.h
+++ b/Windows/src/ProxyBridge.h
@@ -42,6 +42,7 @@ PROXYBRIDGE_API BOOL ProxyBridge_MoveRuleToPosition(UINT32 rule_id, UINT32 new_p
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_SetLocalhostViaProxy(BOOL enable);
PROXYBRIDGE_API void ProxyBridge_SetLogCallback(LogCallback callback);
PROXYBRIDGE_API void ProxyBridge_SetConnectionCallback(ConnectionCallback callback);
PROXYBRIDGE_API void ProxyBridge_SetTrafficLoggingEnabled(BOOL enable);
From fab69128b974f28bc8bacf7b6f409e9e16fe7a61 Mon Sep 17 00:00:00 2001
From: Sourav Kalal
Date: Fri, 13 Feb 2026 21:20:44 +0530
Subject: [PATCH 37/58] #70 added local host proxy on off in GUI and CLI
---
Windows/cli/Program.cs | 16 +++++++++----
Windows/cli/ProxyBridgeNative.cs | 3 +++
Windows/gui/Interop/ProxyBridgeNative.cs | 3 +++
Windows/gui/Resources/Resources.Designer.cs | 6 +++++
Windows/gui/Resources/Resources.resx | 3 +++
Windows/gui/Resources/Resources.zh.resx | 3 +++
Windows/gui/Services/ConfigManager.cs | 1 +
Windows/gui/Services/Loc.cs | 1 +
Windows/gui/Services/ProxyBridgeService.cs | 5 ++++
Windows/gui/ViewModels/MainWindowViewModel.cs | 23 +++++++++++++++++++
Windows/gui/Views/MainWindow.axaml | 8 +++++++
11 files changed, 68 insertions(+), 4 deletions(-)
diff --git a/Windows/cli/Program.cs b/Windows/cli/Program.cs
index 733a3d4..20a4073 100644
--- a/Windows/cli/Program.cs
+++ b/Windows/cli/Program.cs
@@ -108,6 +108,11 @@ static async Task Main(string[] args)
description: "Route DNS queries through proxy (default: true)",
getDefaultValue: () => true);
+ var localhostViaProxyOption = new Option(
+ name: "--localhost-via-proxy",
+ description: "Route localhost traffic through proxy (default: false, most proxies block localhost for SSRF prevention, local traffic to remote proxy will cause issues)",
+ getDefaultValue: () => false);
+
var verboseOption = new Option(
name: "--verbose",
description: "Logging verbosity level\n" +
@@ -125,6 +130,7 @@ static async Task Main(string[] args)
ruleOption,
ruleFileOption,
dnsViaProxyOption,
+ localhostViaProxyOption,
verboseOption
};
@@ -135,10 +141,10 @@ static async Task Main(string[] args)
await CheckAndUpdate();
});
- rootCommand.SetHandler(async (proxyUrl, rules, ruleFile, dnsViaProxy, verbose) =>
+ rootCommand.SetHandler(async (proxyUrl, rules, ruleFile, dnsViaProxy, localhostViaProxy, verbose) =>
{
- await RunProxyBridge(proxyUrl, rules, ruleFile, dnsViaProxy, verbose);
- }, proxyOption, ruleOption, ruleFileOption, dnsViaProxyOption, verboseOption);
+ await RunProxyBridge(proxyUrl, rules, ruleFile, dnsViaProxy, localhostViaProxy, verbose);
+ }, proxyOption, ruleOption, ruleFileOption, dnsViaProxyOption, localhostViaProxyOption, verboseOption);
if (args.Contains("--help") || args.Contains("-h") || args.Contains("-?"))
{
@@ -148,7 +154,7 @@ static async Task Main(string[] args)
return await rootCommand.InvokeAsync(args);
}
- private static async Task RunProxyBridge(string proxyUrl, string[] rules, string? ruleFile, bool dnsViaProxy, int verboseLevel)
+ private static async Task RunProxyBridge(string proxyUrl, string[] rules, string? ruleFile, bool dnsViaProxy, bool localhostViaProxy, int verboseLevel)
{
_verboseLevel = verboseLevel;
ShowBanner();
@@ -201,6 +207,7 @@ private static async Task RunProxyBridge(string proxyUrl, string[] rules, s
Console.WriteLine($"Proxy Auth: {proxyInfo.Username}:***");
}
Console.WriteLine($"DNS via Proxy: {(dnsViaProxy ? "Enabled" : "Disabled")}");
+ Console.WriteLine($"Localhost via Proxy: {(localhostViaProxy ? "Enabled" : "Disabled (Security: most proxies block localhost)")}");
if (!ProxyBridgeNative.ProxyBridge_SetProxyConfig(
proxyInfo.Type,
@@ -214,6 +221,7 @@ private static async Task RunProxyBridge(string proxyUrl, string[] rules, s
}
ProxyBridgeNative.ProxyBridge_SetDnsViaProxy(dnsViaProxy);
+ ProxyBridgeNative.ProxyBridge_SetLocalhostViaProxy(localhostViaProxy);
if (parsedRules.Count > 0)
{
diff --git a/Windows/cli/ProxyBridgeNative.cs b/Windows/cli/ProxyBridgeNative.cs
index fc29a02..071ffd1 100644
--- a/Windows/cli/ProxyBridgeNative.cs
+++ b/Windows/cli/ProxyBridgeNative.cs
@@ -87,6 +87,9 @@ public static extern bool ProxyBridge_SetProxyConfig(
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void ProxyBridge_SetDnsViaProxy([MarshalAs(UnmanagedType.Bool)] bool enable);
+ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void ProxyBridge_SetLocalhostViaProxy([MarshalAs(UnmanagedType.Bool)] bool enable);
+
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ProxyBridge_Start();
diff --git a/Windows/gui/Interop/ProxyBridgeNative.cs b/Windows/gui/Interop/ProxyBridgeNative.cs
index 4451f82..b9ba05f 100644
--- a/Windows/gui/Interop/ProxyBridgeNative.cs
+++ b/Windows/gui/Interop/ProxyBridgeNative.cs
@@ -111,6 +111,9 @@ public static extern bool ProxyBridge_SetProxyConfig(
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void ProxyBridge_SetDnsViaProxy([MarshalAs(UnmanagedType.Bool)] bool enable);
+ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void ProxyBridge_SetLocalhostViaProxy([MarshalAs(UnmanagedType.Bool)] bool enable);
+
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ProxyBridge_Start();
diff --git a/Windows/gui/Resources/Resources.Designer.cs b/Windows/gui/Resources/Resources.Designer.cs
index 95d49cb..f428fd7 100644
--- a/Windows/gui/Resources/Resources.Designer.cs
+++ b/Windows/gui/Resources/Resources.Designer.cs
@@ -92,6 +92,12 @@ internal static string MenuDnsViaProxy {
}
}
+ internal static string MenuLocalhostViaProxy {
+ get {
+ return ResourceManager.GetString("MenuLocalhostViaProxy", resourceCulture);
+ }
+ }
+
internal static string MenuEnableTrafficLogging {
get {
return ResourceManager.GetString("MenuEnableTrafficLogging", resourceCulture);
diff --git a/Windows/gui/Resources/Resources.resx b/Windows/gui/Resources/Resources.resx
index 0b64c18..3615a04 100644
--- a/Windows/gui/Resources/Resources.resx
+++ b/Windows/gui/Resources/Resources.resx
@@ -26,6 +26,9 @@
DNS via Proxy
+
+ Localhost via Proxy
+
Enable Traffic Logging
diff --git a/Windows/gui/Resources/Resources.zh.resx b/Windows/gui/Resources/Resources.zh.resx
index 981fd88..8551641 100644
--- a/Windows/gui/Resources/Resources.zh.resx
+++ b/Windows/gui/Resources/Resources.zh.resx
@@ -26,6 +26,9 @@
DNS通过代理
+
+ 本地主机通过代理
+
启用流量日志
diff --git a/Windows/gui/Services/ConfigManager.cs b/Windows/gui/Services/ConfigManager.cs
index b1b5055..e1d5acc 100644
--- a/Windows/gui/Services/ConfigManager.cs
+++ b/Windows/gui/Services/ConfigManager.cs
@@ -15,6 +15,7 @@ public class AppConfig
public string ProxyUsername { get; set; } = "";
public string ProxyPassword { get; set; } = "";
public bool DnsViaProxy { get; set; } = true;
+ public bool LocalhostViaProxy { get; set; } = false; // Default: disabled
public bool IsTrafficLoggingEnabled { get; set; } = true;
public string Language { get; set; } = "en";
public bool CloseToTray { get; set; } = true;
diff --git a/Windows/gui/Services/Loc.cs b/Windows/gui/Services/Loc.cs
index 43854fc..e2508a1 100644
--- a/Windows/gui/Services/Loc.cs
+++ b/Windows/gui/Services/Loc.cs
@@ -41,6 +41,7 @@ private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
public string MenuProxySettings => Resources.Resources.MenuProxySettings;
public string MenuProxyRules => Resources.Resources.MenuProxyRules;
public string MenuDnsViaProxy => Resources.Resources.MenuDnsViaProxy;
+ public string MenuLocalhostViaProxy => Resources.Resources.MenuLocalhostViaProxy;
public string MenuEnableTrafficLogging => Resources.Resources.MenuEnableTrafficLogging;
public string MenuSettings => Resources.Resources.MenuSettings;
public string MenuCloseToTray => Resources.Resources.MenuCloseToTray;
diff --git a/Windows/gui/Services/ProxyBridgeService.cs b/Windows/gui/Services/ProxyBridgeService.cs
index be93ae3..fa34ce5 100644
--- a/Windows/gui/Services/ProxyBridgeService.cs
+++ b/Windows/gui/Services/ProxyBridgeService.cs
@@ -128,6 +128,11 @@ public void SetDnsViaProxy(bool enable)
ProxyBridgeNative.ProxyBridge_SetDnsViaProxy(enable);
}
+ public void SetLocalhostViaProxy(bool enable)
+ {
+ ProxyBridgeNative.ProxyBridge_SetLocalhostViaProxy(enable);
+ }
+
public static void SetTrafficLoggingEnabled(bool enable)
{
ProxyBridgeNative.ProxyBridge_SetTrafficLoggingEnabled(enable);
diff --git a/Windows/gui/ViewModels/MainWindowViewModel.cs b/Windows/gui/ViewModels/MainWindowViewModel.cs
index 84688d6..58393f0 100644
--- a/Windows/gui/ViewModels/MainWindowViewModel.cs
+++ b/Windows/gui/ViewModels/MainWindowViewModel.cs
@@ -122,6 +122,7 @@ public void SetMainWindow(Window window)
_activityLogTimer.Start();
_proxyService.SetDnsViaProxy(_dnsViaProxy);
+ _proxyService.SetLocalhostViaProxy(_localhostViaProxy);
if (!string.IsNullOrEmpty(_currentProxyIp) &&
!string.IsNullOrEmpty(_currentProxyPort) &&
ushort.TryParse(_currentProxyPort, out ushort portNum))
@@ -288,6 +289,20 @@ public bool DnsViaProxy
}
}
+ private bool _localhostViaProxy = false; // Default: disabled for security
+ public bool LocalhostViaProxy
+ {
+ get => _localhostViaProxy;
+ set
+ {
+ if (SetProperty(ref _localhostViaProxy, value))
+ {
+ _proxyService?.SetLocalhostViaProxy(value);
+ SaveConfigurationInternal();
+ }
+ }
+ }
+
private bool _isTrafficLoggingEnabled = true;
public bool IsTrafficLoggingEnabled
{
@@ -362,6 +377,7 @@ public bool StartWithWindows
public ICommand ShowAboutCommand { get; }
public ICommand CheckForUpdatesCommand { get; }
public ICommand ToggleDnsViaProxyCommand { get; }
+ public ICommand ToggleLocalhostViaProxyCommand { get; }
public ICommand ToggleTrafficLoggingCommand { get; }
public ICommand ToggleCloseToTrayCommand { get; }
public ICommand ToggleStartWithWindowsCommand { get; }
@@ -501,6 +517,11 @@ public MainWindowViewModel()
DnsViaProxy = !DnsViaProxy;
});
+ ToggleLocalhostViaProxyCommand = new RelayCommand(() =>
+ {
+ LocalhostViaProxy = !LocalhostViaProxy;
+ });
+
ToggleTrafficLoggingCommand = new RelayCommand(() =>
{
IsTrafficLoggingEnabled = !IsTrafficLoggingEnabled;
@@ -707,6 +728,7 @@ private void LoadConfiguration()
_currentProxyPassword = config.ProxyPassword ?? "";
DnsViaProxy = config.DnsViaProxy;
+ LocalhostViaProxy = config.LocalhostViaProxy;
CloseToTray = config.CloseToTray;
IsTrafficLoggingEnabled = config.IsTrafficLoggingEnabled;
@@ -763,6 +785,7 @@ private void SaveConfigurationInternalAsync()
ProxyUsername = _currentProxyUsername,
ProxyPassword = _currentProxyPassword,
DnsViaProxy = _dnsViaProxy,
+ LocalhostViaProxy = _localhostViaProxy,
IsTrafficLoggingEnabled = _isTrafficLoggingEnabled,
Language = _currentLanguage,
CloseToTray = _closeToTray,
diff --git a/Windows/gui/Views/MainWindow.axaml b/Windows/gui/Views/MainWindow.axaml
index 76094b8..48b1694 100644
--- a/Windows/gui/Views/MainWindow.axaml
+++ b/Windows/gui/Views/MainWindow.axaml
@@ -221,6 +221,14 @@
Margin="0"/>
+
+
+
+
+
Date: Fri, 13 Feb 2026 21:33:38 +0530
Subject: [PATCH 38/58] added exe checker in windows update
---
Windows/cli/Program.cs | 20 ++++++++++++--------
Windows/gui/Services/UpdateService.cs | 8 +++++++-
2 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/Windows/cli/Program.cs b/Windows/cli/Program.cs
index 20a4073..5dd8553 100644
--- a/Windows/cli/Program.cs
+++ b/Windows/cli/Program.cs
@@ -494,12 +494,8 @@ private static async Task CheckAndUpdate()
return;
}
- Console.ForegroundColor = ConsoleColor.Yellow;
- Console.WriteLine($"⚠ New version available: {releaseName}");
- Console.ResetColor();
- Console.WriteLine();
-
-
+ // Check if Windows installer exists before showing update available
+ // (handles cross-platform releases where v4.0 might be released for Linux only)
var assets = root.GetProperty("assets").EnumerateArray();
string? setupUrl = null;
string? setupName = null;
@@ -519,13 +515,21 @@ private static async Task CheckAndUpdate()
if (string.IsNullOrEmpty(setupUrl))
{
- Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine("ERROR: Setup installer not found in latest release.");
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.WriteLine($"ℹ Version {latestVersionStr} exists but Windows installer not yet available.");
+ Console.WriteLine($" (Release might be for other platforms only)");
Console.ResetColor();
+ Console.WriteLine();
+ Console.WriteLine("You are using the latest version available for Windows.");
Console.WriteLine($"Visit: https://github.com/{repoOwner}/{repoName}/releases/latest");
return;
}
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.WriteLine($"⚠ New version available: {releaseName}");
+ Console.ResetColor();
+ Console.WriteLine();
+
Console.WriteLine($"Downloading: {setupName}");
Console.WriteLine($"From: {setupUrl}");
Console.WriteLine();
diff --git a/Windows/gui/Services/UpdateService.cs b/Windows/gui/Services/UpdateService.cs
index 19b7091..3699ae3 100644
--- a/Windows/gui/Services/UpdateService.cs
+++ b/Windows/gui/Services/UpdateService.cs
@@ -45,11 +45,17 @@ public async Task CheckForUpdatesAsync()
a.Name.Contains("installer", StringComparison.OrdinalIgnoreCase) ||
a.Name.Contains("ProxyBridge", StringComparison.OrdinalIgnoreCase)));
+ // Only mark update as available if:
+ // 1. Version is newer AND
+ // 2. Windows installer (.exe) exists in release (platform-specific check)
+ var hasWindowsInstaller = setupAsset != null && !string.IsNullOrEmpty(setupAsset.BrowserDownloadUrl);
+ var isNewerVersion = IsNewerVersion(latestVersion, currentVersion);
+
return new VersionInfo
{
CurrentVersion = currentVersion,
LatestVersion = latestVersion,
- IsUpdateAvailable = IsNewerVersion(latestVersion, currentVersion),
+ IsUpdateAvailable = isNewerVersion && hasWindowsInstaller,
LatestVersionString = release?.TagName ?? "Unknown",
CurrentVersionString = FormatVersion(currentVersion),
DownloadUrl = setupAsset?.BrowserDownloadUrl,
From 5b46b8854b067384a122541ec9e8281781a0c4c2 Mon Sep 17 00:00:00 2001
From: Sourav Kalal
Date: Fri, 13 Feb 2026 22:00:10 +0530
Subject: [PATCH 39/58] Windows Version 3.2.0
---
Windows/README.md | 2 +-
Windows/cli/Program.cs | 2 +-
Windows/cli/ProxyBridge.CLI.csproj | 2 +-
Windows/compile.ps1 | 2 +-
Windows/gui/ProxyBridge.GUI.csproj | 6 +++---
Windows/gui/app.manifest | 2 +-
Windows/installer/ProxyBridge.nsi | 4 ++--
Windows/src/ProxyBridge.c | 2 +-
8 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/Windows/README.md b/Windows/README.md
index 6ae4ed4..c0ad17a 100644
--- a/Windows/README.md
+++ b/Windows/README.md
@@ -209,7 +209,7 @@ ProxyBridge_CLI.exe -h
| |_) | '__/ _ \ \/ / | | | | _ \| '__| |/ _` |/ _` |/ _ \
| __/| | | (_) > <| |_| | | |_) | | | | (_| | (_| | __/
|_| |_| \___/_/\_\\__, | |____/|_| |_|\__,_|\__, |\___|
- |___/ |___/ V3.1.0
+ |___/ |___/ V3.2.0
Universal proxy client for Windows applications
diff --git a/Windows/cli/Program.cs b/Windows/cli/Program.cs
index 5dd8553..e9fba4d 100644
--- a/Windows/cli/Program.cs
+++ b/Windows/cli/Program.cs
@@ -429,7 +429,7 @@ private static void ShowBanner()
Console.WriteLine(" | |_) | '__/ _ \\ \\/ / | | | | _ \\| '__| |/ _` |/ _` |/ _ \\");
Console.WriteLine(" | __/| | | (_) > <| |_| | | |_) | | | | (_| | (_| | __/");
Console.WriteLine(" |_| |_| \\___/_/\\_\\\\__, | |____/|_| |_|\\__,_|\\__, |\\___|");
- var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "3.1.0";
+ var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "3.2.0";
Console.WriteLine($" |___/ |___/ V{version}");
Console.WriteLine();
Console.WriteLine(" Universal proxy client for Windows applications");
diff --git a/Windows/cli/ProxyBridge.CLI.csproj b/Windows/cli/ProxyBridge.CLI.csproj
index 4502e24..8b63b01 100644
--- a/Windows/cli/ProxyBridge.CLI.csproj
+++ b/Windows/cli/ProxyBridge.CLI.csproj
@@ -6,7 +6,7 @@
enable
enable
ProxyBridge_CLI
- 3.1.0
+ 3.2.0
true
false
true
diff --git a/Windows/compile.ps1 b/Windows/compile.ps1
index ec3cfd4..6bab012 100644
--- a/Windows/compile.ps1
+++ b/Windows/compile.ps1
@@ -296,7 +296,7 @@ if ($success) {
Pop-Location
if ($LASTEXITCODE -eq 0) {
Write-Host " Installer created successfully" -ForegroundColor Green
- $installerName = "ProxyBridge-Setup-3.1.0.exe"
+ $installerName = "ProxyBridge-Setup-3.2.0.exe"
if (Test-Path "installer\$installerName") {
Move-Item "installer\$installerName" -Destination $OutputDir -Force
Write-Host " Moved: $installerName -> $OutputDir\" -ForegroundColor Gray
diff --git a/Windows/gui/ProxyBridge.GUI.csproj b/Windows/gui/ProxyBridge.GUI.csproj
index ace47c5..177c950 100644
--- a/Windows/gui/ProxyBridge.GUI.csproj
+++ b/Windows/gui/ProxyBridge.GUI.csproj
@@ -7,9 +7,9 @@
app.manifest
ProxyBridge
ProxyBridge.GUI
- 3.1.0
- 3.1.0
- 3.1.0
+ 3.2.0
+ 3.2.0
+ 3.2.0
Assets\logo.ico
InterceptSuite
ProxyBridge
diff --git a/Windows/gui/app.manifest b/Windows/gui/app.manifest
index 3b02a12..0011b3c 100644
--- a/Windows/gui/app.manifest
+++ b/Windows/gui/app.manifest
@@ -1,7 +1,7 @@
Date: Sun, 15 Feb 2026 21:28:47 +0530
Subject: [PATCH 40/58] #67 fixed macos version issue
---
.../ProxyBridge.xcodeproj/project.pbxproj | 27 ++++++++++++++-----
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj b/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
index 26ead15..d9e7683 100644
--- a/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
+++ b/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
@@ -7,6 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
+ 1B7672912F421AB400B323D6 /* Signing-Config-app.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 1B7672902F421AB400B323D6 /* Signing-Config-app.xcconfig */; };
+ 1B7672922F421AB400B323D6 /* Signing-Config-app.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 1B7672902F421AB400B323D6 /* Signing-Config-app.xcconfig */; };
+ 1B7672942F421ABA00B323D6 /* Signing-Config-ext.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 1B7672932F421ABA00B323D6 /* Signing-Config-ext.xcconfig */; };
+ 1B7672952F421ABA00B323D6 /* Signing-Config-ext.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 1B7672932F421ABA00B323D6 /* Signing-Config-ext.xcconfig */; };
8466A1E92EC63C5600A8C2E5 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8466A1E82EC63C5600A8C2E5 /* NetworkExtension.framework */; };
8466A1F32EC63C5600A8C2E5 /* com.interceptsuite.ProxyBridge.extension.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 8466A1E62EC63C5600A8C2E5 /* com.interceptsuite.ProxyBridge.extension.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
846F78F92F14204F00C94754 /* Signing-Config-ext.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 846F78F82F14204F00C94754 /* Signing-Config-ext.xcconfig */; };
@@ -44,6 +48,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 1B7672902F421AB400B323D6 /* Signing-Config-app.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Signing-Config-app.xcconfig"; sourceTree = ""; };
+ 1B7672932F421ABA00B323D6 /* Signing-Config-ext.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Signing-Config-ext.xcconfig"; sourceTree = ""; };
8466A1D32EC63B9400A8C2E5 /* ProxyBridge.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProxyBridge.app; sourceTree = BUILT_PRODUCTS_DIR; };
8466A1E62EC63C5600A8C2E5 /* com.interceptsuite.ProxyBridge.extension.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = com.interceptsuite.ProxyBridge.extension.systemextension; sourceTree = BUILT_PRODUCTS_DIR; };
8466A1E82EC63C5600A8C2E5 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
@@ -109,6 +115,8 @@
8466A1D42EC63B9400A8C2E5 /* Products */,
84AFF4662F23CAC8007CF0B0 /* Signing-Config-app.xcconfig */,
84AFF4692F23CAD5007CF0B0 /* Signing-Config-ext.xcconfig */,
+ 1B7672902F421AB400B323D6 /* Signing-Config-app.xcconfig */,
+ 1B7672932F421ABA00B323D6 /* Signing-Config-ext.xcconfig */,
);
sourceTree = "";
};
@@ -223,8 +231,10 @@
files = (
846F78FA2F14204F00C94754 /* Signing-Config-ext.xcconfig in Resources */,
84AFF46B2F23CAD5007CF0B0 /* Signing-Config-ext.xcconfig in Resources */,
+ 1B7672912F421AB400B323D6 /* Signing-Config-app.xcconfig in Resources */,
84AFF4672F23CAC8007CF0B0 /* Signing-Config-app.xcconfig in Resources */,
84B1D8012F141B2300D0E39B /* Signing-Config-app.xcconfig in Resources */,
+ 1B7672952F421ABA00B323D6 /* Signing-Config-ext.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -232,10 +242,12 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 1B7672942F421ABA00B323D6 /* Signing-Config-ext.xcconfig in Resources */,
846F78F92F14204F00C94754 /* Signing-Config-ext.xcconfig in Resources */,
84B1D8022F141B2300D0E39B /* Signing-Config-app.xcconfig in Resources */,
84AFF46A2F23CAD5007CF0B0 /* Signing-Config-ext.xcconfig in Resources */,
84AFF4682F23CAC8007CF0B0 /* Signing-Config-app.xcconfig in Resources */,
+ 1B7672922F421AB400B323D6 /* Signing-Config-app.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -398,7 +410,6 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = "";
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
@@ -422,7 +433,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MARKETING_VERSION = 3.1;
+ MARKETING_VERSION = 3.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.interceptsuite.ProxyBridge;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -438,7 +449,7 @@
};
8466A1E02EC63B9500A8C2E5 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 84AFF4662F23CAC8007CF0B0 /* Signing-Config-app.xcconfig */;
+ baseConfigurationReference = 1B7672902F421AB400B323D6 /* Signing-Config-app.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@@ -467,7 +478,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MARKETING_VERSION = 3.1;
+ MARKETING_VERSION = 3.1.0;
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ProxyBridge Prod";
REGISTER_APP_GROUPS = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -507,7 +519,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
- MARKETING_VERSION = 3.1;
+ MARKETING_VERSION = 3.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.interceptsuite.ProxyBridge.extension;
PRODUCT_NAME = "$(inherited)";
REGISTER_APP_GROUPS = YES;
@@ -522,7 +534,7 @@
};
8466A1F72EC63C5600A8C2E5 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 84AFF4692F23CAD5007CF0B0 /* Signing-Config-ext.xcconfig */;
+ baseConfigurationReference = 1B7672932F421ABA00B323D6 /* Signing-Config-ext.xcconfig */;
buildSettings = {
CURRENT_PROJECT_VERSION = 1;
ENABLE_APP_SANDBOX = YES;
@@ -546,7 +558,8 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
- MARKETING_VERSION = 3.1;
+ MARKETING_VERSION = 3.1.0;
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ProxyBridge Extension Prod";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
From 3614c88b0bff09df14a0ee7d7fd87f271677e4cd Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Tue, 17 Feb 2026 16:25:21 +0530
Subject: [PATCH 41/58] #68 added process name support for MacOS, connection
log uses process name in mac
---
.../extension/AppProxyProvider.swift | 73 +++++++++++++++----
1 file changed, 59 insertions(+), 14 deletions(-)
diff --git a/MacOS/ProxyBridge/extension/AppProxyProvider.swift b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
index ac6cd18..731490e 100644
--- a/MacOS/ProxyBridge/extension/AppProxyProvider.swift
+++ b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
@@ -1,4 +1,5 @@
import NetworkExtension
+import Foundation
enum RuleProtocol: String, Codable {
case tcp = "TCP"
@@ -52,8 +53,18 @@ struct ProxyRule: Codable {
self.enabled = enabled
}
- func matchesProcess(_ processPath: String) -> Bool {
- return Self.matchProcessList(processNames, processPath: processPath)
+ func matchesProcess(bundleId: String, processName: String?) -> Bool {
+ if Self.matchProcessList(processNames, processPath: bundleId) {
+ return true
+ }
+
+ if let procName = processName {
+ if Self.matchProcessList(processNames, processPath: procName) {
+ return true
+ }
+ }
+
+ return false
}
func matchesIP(_ ipString: String) -> Bool {
@@ -189,6 +200,34 @@ class AppProxyProvider: NETransparentProxyProvider {
private var logQueue: [[String: String]] = []
private let logQueueLock = NSLock()
+ private func getProcessName(from metaData: NEFlowMetaData) -> String? {
+ guard let auditTokenData = metaData.sourceAppAuditToken else {
+ return nil
+ }
+ guard auditTokenData.count == MemoryLayout.size else {
+ return nil
+ }
+
+ let pid = auditTokenData.withUnsafeBytes { ptr -> pid_t in
+ guard let baseAddress = ptr.baseAddress else { return 0 }
+ let token = baseAddress.assumingMemoryBound(to: UInt32.self)
+ return pid_t(token[5])
+ }
+
+ guard pid > 0 else {
+ return nil
+ }
+
+ var pathBuffer = [Int8](repeating: 0, count: Int(MAXPATHLEN))
+ guard proc_pidpath(pid, &pathBuffer, UInt32(MAXPATHLEN)) > 0 else {
+ return nil
+ }
+
+ let fullPath = String(cString: pathBuffer)
+ let processName = (fullPath as NSString).lastPathComponent
+ return processName
+ }
+
private var trafficLoggingEnabled = true
private var rules: [ProxyRule] = []
@@ -448,10 +487,14 @@ class AppProxyProvider: NETransparentProxyProvider {
}
var processPath = "unknown"
+ var processName: String?
if let metaData = flow.metaData as? NEFlowMetaData {
processPath = metaData.sourceAppSigningIdentifier
+ processName = getProcessName(from: metaData)
}
+ let displayName = processName ?? processPath
+
if processPath == "com.interceptsuite.ProxyBridge" || processPath == "com.interceptsuite.ProxyBridge.extension" {
return false
}
@@ -461,16 +504,16 @@ class AppProxyProvider: NETransparentProxyProvider {
proxyLock.unlock()
if !hasProxyConfig {
- sendLogToApp(protocol: "TCP", process: processPath, destination: destination, port: portStr, proxy: "Direct")
+ sendLogToApp(protocol: "TCP", process: displayName, destination: destination, port: portStr, proxy: "Direct")
return false
}
- let matchedRule = findMatchingRule(processPath: processPath, destination: destination, port: portNum, connectionProtocol: .tcp, checkIpPort: true)
+ let matchedRule = findMatchingRule(bundleId: processPath, processName: processName, destination: destination, port: portNum, connectionProtocol: .tcp, checkIpPort: true)
if let rule = matchedRule {
let action = rule.action.rawValue
- sendLogToApp(protocol: "TCP", process: processPath, destination: destination, port: portStr, proxy: action)
+ sendLogToApp(protocol: "TCP", process: displayName, destination: destination, port: portStr, proxy: action)
switch rule.action {
case .direct:
@@ -484,17 +527,21 @@ class AppProxyProvider: NETransparentProxyProvider {
return true
}
} else {
- sendLogToApp(protocol: "TCP", process: processPath, destination: destination, port: portStr, proxy: "Direct")
+ sendLogToApp(protocol: "TCP", process: displayName, destination: destination, port: portStr, proxy: "Direct")
return false
}
}
private func handleUDPFlow(_ flow: NEAppProxyUDPFlow) -> Bool {
var processPath = "unknown"
+ var processName: String?
if let metaData = flow.metaData as? NEFlowMetaData {
processPath = metaData.sourceAppSigningIdentifier
+ processName = getProcessName(from: metaData)
}
+ let displayName = processName ?? processPath
+
if processPath == "com.interceptsuite.ProxyBridge" || processPath == "com.interceptsuite.ProxyBridge.extension" {
return false
}
@@ -509,16 +556,15 @@ class AppProxyProvider: NETransparentProxyProvider {
return false
}
- let matchedRule = findMatchingRule(processPath: processPath, destination: "", port: 0, connectionProtocol: .udp, checkIpPort: false)
+ let matchedRule = findMatchingRule(bundleId: processPath, processName: processName, destination: "", port: 0, connectionProtocol: .udp, checkIpPort: false)
if let rule = matchedRule {
- // We don't have access to UDP dest ip and port when os handles it in (apple proxy API limitation), we log with unknown ip and port to know specific package is using UDP
switch rule.action {
case .direct:
- sendLogToApp(protocol: "UDP", process: processPath, destination: "unknown", port: "unknown", proxy: "Direct")
+ sendLogToApp(protocol: "UDP", process: displayName, destination: "unknown", port: "unknown", proxy: "Direct")
return false
case .block:
- sendLogToApp(protocol: "UDP", process: processPath, destination: "unknown", port: "unknown", proxy: "BLOCK")
+ sendLogToApp(protocol: "UDP", process: displayName, destination: "unknown", port: "unknown", proxy: "BLOCK")
return true
case .proxy:
flow.open(withLocalEndpoint: nil) { [weak self] error in
@@ -536,8 +582,7 @@ class AppProxyProvider: NETransparentProxyProvider {
return true
}
} else {
- // No rule matched let OS handle it, but log it so user knows this process is using UDP
- sendLogToApp(protocol: "UDP", process: processPath, destination: "unknown", port: "unknown", proxy: "Direct")
+ sendLogToApp(protocol: "UDP", process: displayName, destination: "unknown", port: "unknown", proxy: "Direct")
return false
}
}
@@ -1150,7 +1195,7 @@ class AppProxyProvider: NETransparentProxyProvider {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
}
- private func findMatchingRule(processPath: String, destination: String, port: UInt16, connectionProtocol: RuleProtocol, checkIpPort: Bool) -> ProxyRule? {
+ private func findMatchingRule(bundleId: String, processName: String?, destination: String, port: UInt16, connectionProtocol: RuleProtocol, checkIpPort: Bool) -> ProxyRule? {
rulesLock.lock()
defer { rulesLock.unlock() }
@@ -1161,7 +1206,7 @@ class AppProxyProvider: NETransparentProxyProvider {
continue
}
- if !rule.matchesProcess(processPath) {
+ if !rule.matchesProcess(bundleId: bundleId, processName: processName) {
continue
}
From 82a4e674b8335b7e864e56f3325524bf52d7e54c Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Tue, 17 Feb 2026 21:34:26 +0530
Subject: [PATCH 42/58] #68 Fixed Rule checker priority
---
.../extension/AppProxyProvider.swift | 53 +++++++++++++++----
1 file changed, 43 insertions(+), 10 deletions(-)
diff --git a/MacOS/ProxyBridge/extension/AppProxyProvider.swift b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
index 731490e..c12ff2a 100644
--- a/MacOS/ProxyBridge/extension/AppProxyProvider.swift
+++ b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
@@ -1197,30 +1197,63 @@ class AppProxyProvider: NETransparentProxyProvider {
private func findMatchingRule(bundleId: String, processName: String?, destination: String, port: UInt16, connectionProtocol: RuleProtocol, checkIpPort: Bool) -> ProxyRule? {
rulesLock.lock()
- defer { rulesLock.unlock() }
+ let currentRules = rules
+ rulesLock.unlock()
- for rule in rules {
+ var wildcardRule: ProxyRule? = nil
+
+ for rule in currentRules {
guard rule.enabled else { continue }
+ // check protocol firstß
if rule.ruleProtocol != .both && rule.ruleProtocol != connectionProtocol {
continue
}
- if !rule.matchesProcess(bundleId: bundleId, processName: processName) {
- continue
- }
+ // if this is a wildcard process rule
+ let isWildcardProcess = (rule.processNames == "*" || rule.processNames.isEmpty)
- if checkIpPort {
- if !rule.matchesIP(destination) {
+ if isWildcardProcess {
+ // wildcard has specific filters
+ let hasIpFilter = (rule.targetHosts != "*" && !rule.targetHosts.isEmpty)
+ let hasPortFilter = (rule.targetPorts != "*" && !rule.targetPorts.isEmpty)
+
+ if hasIpFilter || hasPortFilter {
+ // wildcard with filters - check immediately
+ if checkIpPort {
+ if rule.matchesIP(destination) && rule.matchesPort(port) {
+ return rule
+ }
+ } else {
+ // for UDP without destination info, skip filtered wildcards
+ }
continue
}
- if !rule.matchesPort(port) {
- continue
+ // Fully wildcard rule - defer it (save first one only)
+ if wildcardRule == nil {
+ wildcardRule = rule
}
+ continue
}
- return rule
+ // Specific process rule - check if it matches
+ if rule.matchesProcess(bundleId: bundleId, processName: processName) {
+ // Process matched! Check IP and port filters
+ if checkIpPort {
+ if rule.matchesIP(destination) && rule.matchesPort(port) {
+ return rule
+ }
+ } else {
+ // For UDP without destination info, match on process only
+ return rule
+ }
+ }
+ }
+
+ // No specific rule matched, use deferred wildcard if available
+ if let wildcardRule = wildcardRule {
+ return wildcardRule
}
return nil
From 0cb1aaa1bcfc74e813dcaff6e2153c4cdf89a42f Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Wed, 18 Feb 2026 02:22:19 +0530
Subject: [PATCH 43/58] removed GUI domain, added IP range for mac
---
.../ProxyBridge/ProxyRulesView.swift | 2 +-
.../extension/AppProxyProvider.swift | 29 +++++++++++++++++++
2 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/MacOS/ProxyBridge/ProxyBridge/ProxyRulesView.swift b/MacOS/ProxyBridge/ProxyBridge/ProxyRulesView.swift
index c6609b9..617237a 100644
--- a/MacOS/ProxyBridge/ProxyBridge/ProxyRulesView.swift
+++ b/MacOS/ProxyBridge/ProxyBridge/ProxyRulesView.swift
@@ -410,7 +410,7 @@ struct RuleEditorView: View {
label: "Target hosts",
placeholder: "*",
text: $targetHosts,
- hint: "Example: 127.0.0.1; *.example.com; 192.168.1.*; 10.1.0.0-10.5.255.255"
+ hint: "Example: 127.0.0.1; 192.168.1.*; 10.0.0.1-10.0.0.254"
)
formField(
diff --git a/MacOS/ProxyBridge/extension/AppProxyProvider.swift b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
index c12ff2a..bca221c 100644
--- a/MacOS/ProxyBridge/extension/AppProxyProvider.swift
+++ b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
@@ -135,11 +135,40 @@ struct ProxyRule: Codable {
return false
}
+ private static func ipToInteger(_ ipString: String) -> UInt32? {
+ let octets = ipString.components(separatedBy: ".")
+ guard octets.count == 4 else { return nil }
+
+ var result: UInt32 = 0
+ for octet in octets {
+ guard let value = UInt8(octet) else { return nil }
+ result = (result << 8) | UInt32(value)
+ }
+ return result
+ }
+
private static func matchIPPattern(_ pattern: String, ipString: String) -> Bool {
if pattern.isEmpty || pattern == "*" {
return true
}
+ // Check for IP range (e.g., 192.168.1.1-192.168.1.254)
+ if pattern.contains("-") {
+ let parts = pattern.components(separatedBy: "-")
+ if parts.count == 2 {
+ let startIP = parts[0].trimmingCharacters(in: .whitespaces)
+ let endIP = parts[1].trimmingCharacters(in: .whitespaces)
+
+ if let startInt = ipToInteger(startIP),
+ let endInt = ipToInteger(endIP),
+ let targetInt = ipToInteger(ipString) {
+ return targetInt >= startInt && targetInt <= endInt
+ }
+ }
+ return false
+ }
+
+ // Wildcard matching (e.g., 192.168.1.*)
let patternOctets = pattern.components(separatedBy: ".")
let ipOctets = ipString.components(separatedBy: ".")
From 6fab55ea35ab16b43150ab24f53b454a2bf7b7b5 Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Wed, 18 Feb 2026 02:38:55 +0530
Subject: [PATCH 44/58] removed unwanted resource
---
.../ProxyBridge.xcodeproj/project.pbxproj | 24 -------------------
1 file changed, 24 deletions(-)
diff --git a/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj b/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
index d9e7683..2f3500c 100644
--- a/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
+++ b/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
@@ -7,20 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
- 1B7672912F421AB400B323D6 /* Signing-Config-app.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 1B7672902F421AB400B323D6 /* Signing-Config-app.xcconfig */; };
- 1B7672922F421AB400B323D6 /* Signing-Config-app.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 1B7672902F421AB400B323D6 /* Signing-Config-app.xcconfig */; };
- 1B7672942F421ABA00B323D6 /* Signing-Config-ext.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 1B7672932F421ABA00B323D6 /* Signing-Config-ext.xcconfig */; };
- 1B7672952F421ABA00B323D6 /* Signing-Config-ext.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 1B7672932F421ABA00B323D6 /* Signing-Config-ext.xcconfig */; };
8466A1E92EC63C5600A8C2E5 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8466A1E82EC63C5600A8C2E5 /* NetworkExtension.framework */; };
8466A1F32EC63C5600A8C2E5 /* com.interceptsuite.ProxyBridge.extension.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 8466A1E62EC63C5600A8C2E5 /* com.interceptsuite.ProxyBridge.extension.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- 846F78F92F14204F00C94754 /* Signing-Config-ext.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 846F78F82F14204F00C94754 /* Signing-Config-ext.xcconfig */; };
- 846F78FA2F14204F00C94754 /* Signing-Config-ext.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 846F78F82F14204F00C94754 /* Signing-Config-ext.xcconfig */; };
- 84AFF4672F23CAC8007CF0B0 /* Signing-Config-app.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 84AFF4662F23CAC8007CF0B0 /* Signing-Config-app.xcconfig */; };
- 84AFF4682F23CAC8007CF0B0 /* Signing-Config-app.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 84AFF4662F23CAC8007CF0B0 /* Signing-Config-app.xcconfig */; };
- 84AFF46A2F23CAD5007CF0B0 /* Signing-Config-ext.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 84AFF4692F23CAD5007CF0B0 /* Signing-Config-ext.xcconfig */; };
- 84AFF46B2F23CAD5007CF0B0 /* Signing-Config-ext.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 84AFF4692F23CAD5007CF0B0 /* Signing-Config-ext.xcconfig */; };
- 84B1D8012F141B2300D0E39B /* Signing-Config-app.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 84B1D8002F141B2300D0E39B /* Signing-Config-app.xcconfig */; };
- 84B1D8022F141B2300D0E39B /* Signing-Config-app.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 84B1D8002F141B2300D0E39B /* Signing-Config-app.xcconfig */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -229,12 +217,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 846F78FA2F14204F00C94754 /* Signing-Config-ext.xcconfig in Resources */,
- 84AFF46B2F23CAD5007CF0B0 /* Signing-Config-ext.xcconfig in Resources */,
- 1B7672912F421AB400B323D6 /* Signing-Config-app.xcconfig in Resources */,
- 84AFF4672F23CAC8007CF0B0 /* Signing-Config-app.xcconfig in Resources */,
- 84B1D8012F141B2300D0E39B /* Signing-Config-app.xcconfig in Resources */,
- 1B7672952F421ABA00B323D6 /* Signing-Config-ext.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -242,12 +224,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 1B7672942F421ABA00B323D6 /* Signing-Config-ext.xcconfig in Resources */,
- 846F78F92F14204F00C94754 /* Signing-Config-ext.xcconfig in Resources */,
- 84B1D8022F141B2300D0E39B /* Signing-Config-app.xcconfig in Resources */,
- 84AFF46A2F23CAD5007CF0B0 /* Signing-Config-ext.xcconfig in Resources */,
- 84AFF4682F23CAC8007CF0B0 /* Signing-Config-app.xcconfig in Resources */,
- 1B7672922F421AB400B323D6 /* Signing-Config-app.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
From 811897809ab1b6ed990cb00acf99dd0b480b366c Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Wed, 18 Feb 2026 13:01:58 +0530
Subject: [PATCH 45/58] improved mac logging
---
.../extension/AppProxyProvider.swift | 49 +++++++++++++------
1 file changed, 35 insertions(+), 14 deletions(-)
diff --git a/MacOS/ProxyBridge/extension/AppProxyProvider.swift b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
index bca221c..9458a91 100644
--- a/MacOS/ProxyBridge/extension/AppProxyProvider.swift
+++ b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
@@ -226,8 +226,14 @@ struct ProxyRule: Codable {
class AppProxyProvider: NETransparentProxyProvider {
- private var logQueue: [[String: String]] = []
+ // circular buffer for log queue to avoids O(n) removeFirst() on array
+ private static let logCapacity = 1000
+ private var logBuffer: [[String: String]] = Array(repeating: [:], count: AppProxyProvider.logCapacity)
+ private var logHead = 0 // next read position
+ private var logTail = 0 // next write position
+ private var logCount = 0
private let logQueueLock = NSLock()
+ private let dateFormatter: ISO8601DateFormatter = ISO8601DateFormatter()
private func getProcessName(from metaData: NEFlowMetaData) -> String? {
guard let auditTokenData = metaData.sourceAppAuditToken else {
@@ -257,7 +263,11 @@ class AppProxyProvider: NETransparentProxyProvider {
return processName
}
- private var trafficLoggingEnabled = true
+ private var _trafficLoggingEnabled: Int32 = 1 // atomic: 1=enabled, 0=disabled
+ private var trafficLoggingEnabled: Bool {
+ get { return OSAtomicAdd32(0, &_trafficLoggingEnabled) != 0 }
+ set { OSAtomicCompareAndSwap32(newValue ? 0 : 1, newValue ? 1 : 0, &_trafficLoggingEnabled) }
+ }
private var rules: [ProxyRule] = []
private let rulesLock = NSLock()
@@ -271,17 +281,19 @@ class AppProxyProvider: NETransparentProxyProvider {
private let proxyLock = NSLock()
private func log(_ message: String, level: String = "INFO") {
- let timestamp = ISO8601DateFormatter().string(from: Date())
let logEntry: [String: String] = [
- "timestamp": timestamp,
+ "timestamp": dateFormatter.string(from: Date()),
"level": level,
"message": message
]
-
logQueueLock.lock()
- logQueue.append(logEntry)
- if logQueue.count > 1000 {
- logQueue.removeFirst()
+ logBuffer[logTail] = logEntry
+ logTail = (logTail + 1) % AppProxyProvider.logCapacity
+ if logCount < AppProxyProvider.logCapacity {
+ logCount += 1
+ } else {
+ // Buffer full: advance head to overwrite oldest entry
+ logHead = (logHead + 1) % AppProxyProvider.logCapacity
}
logQueueLock.unlock()
}
@@ -322,9 +334,15 @@ class AppProxyProvider: NETransparentProxyProvider {
switch action {
case "getLogs":
logQueueLock.lock()
- if !logQueue.isEmpty {
- let logsToSend = Array(logQueue.prefix(min(100, logQueue.count)))
- logQueue.removeFirst(logsToSend.count)
+ if logCount > 0 {
+ let batchSize = min(100, logCount)
+ var logsToSend: [[String: String]] = []
+ logsToSend.reserveCapacity(batchSize)
+ for _ in 0.. 1000 {
- logQueue.removeFirst()
+ logBuffer[logTail] = logData
+ logTail = (logTail + 1) % AppProxyProvider.logCapacity
+ if logCount < AppProxyProvider.logCapacity {
+ logCount += 1
+ } else {
+ logHead = (logHead + 1) % AppProxyProvider.logCapacity
}
logQueueLock.unlock()
}
From c5b5f486af3a454e7ca7a8a74890aa2e066fc67f Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Wed, 18 Feb 2026 13:17:54 +0530
Subject: [PATCH 46/58] improved macos network speed added cache for process
name, exit check
---
.../extension/AppProxyProvider.swift | 47 +++++++++++++------
1 file changed, 33 insertions(+), 14 deletions(-)
diff --git a/MacOS/ProxyBridge/extension/AppProxyProvider.swift b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
index 9458a91..856ca3e 100644
--- a/MacOS/ProxyBridge/extension/AppProxyProvider.swift
+++ b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
@@ -226,7 +226,7 @@ struct ProxyRule: Codable {
class AppProxyProvider: NETransparentProxyProvider {
- // circular buffer for log queue to avoids O(n) removeFirst() on array
+ // Circular buffer for log queue - avoids O(n) removeFirst() on array
private static let logCapacity = 1000
private var logBuffer: [[String: String]] = Array(repeating: [:], count: AppProxyProvider.logCapacity)
private var logHead = 0 // next read position
@@ -235,6 +235,11 @@ class AppProxyProvider: NETransparentProxyProvider {
private let logQueueLock = NSLock()
private let dateFormatter: ISO8601DateFormatter = ISO8601DateFormatter()
+ // cache results so each process only instead of per-connection.
+ private var pidCache: [pid_t: String] = [:]
+ private let pidCacheLock = NSLock()
+ private static let pidCacheMaxSize = 256
+
private func getProcessName(from metaData: NEFlowMetaData) -> String? {
guard let auditTokenData = metaData.sourceAppAuditToken else {
return nil
@@ -249,10 +254,17 @@ class AppProxyProvider: NETransparentProxyProvider {
return pid_t(token[5])
}
- guard pid > 0 else {
- return nil
+ guard pid > 0 else { return nil }
+
+ // Check cache first
+ pidCacheLock.lock()
+ if let cached = pidCache[pid] {
+ pidCacheLock.unlock()
+ return cached
}
+ pidCacheLock.unlock()
+ // Cache miss - call proc_pidpath()
var pathBuffer = [Int8](repeating: 0, count: Int(MAXPATHLEN))
guard proc_pidpath(pid, &pathBuffer, UInt32(MAXPATHLEN)) > 0 else {
return nil
@@ -260,6 +272,15 @@ class AppProxyProvider: NETransparentProxyProvider {
let fullPath = String(cString: pathBuffer)
let processName = (fullPath as NSString).lastPathComponent
+
+ // Store in cache (evict all if too large - processes rarely exceed this)
+ pidCacheLock.lock()
+ if pidCache.count >= AppProxyProvider.pidCacheMaxSize {
+ pidCache.removeAll(keepingCapacity: true)
+ }
+ pidCache[pid] = processName
+ pidCacheLock.unlock()
+
return processName
}
@@ -519,6 +540,14 @@ class AppProxyProvider: NETransparentProxyProvider {
}
private func handleTCPFlow(_ flow: NEAppProxyTCPFlow) -> Bool {
+ let metaData = flow.metaData
+ let processPath = metaData.sourceAppSigningIdentifier
+
+ // early exit for own app traffic before any other work
+ if processPath == "com.interceptsuite.ProxyBridge" || processPath == "com.interceptsuite.ProxyBridge.extension" {
+ return false
+ }
+
let remoteEndpoint = flow.remoteEndpoint
var destination = ""
var portNum: UInt16 = 0
@@ -533,19 +562,9 @@ class AppProxyProvider: NETransparentProxyProvider {
portStr = "unknown"
}
- var processPath = "unknown"
- var processName: String?
- if let metaData = flow.metaData as? NEFlowMetaData {
- processPath = metaData.sourceAppSigningIdentifier
- processName = getProcessName(from: metaData)
- }
-
+ let processName = getProcessName(from: metaData)
let displayName = processName ?? processPath
- if processPath == "com.interceptsuite.ProxyBridge" || processPath == "com.interceptsuite.ProxyBridge.extension" {
- return false
- }
-
proxyLock.lock()
let hasProxyConfig = (proxyHost != nil && proxyPort != nil)
proxyLock.unlock()
From 25183f132b2781896393b349c45ce12de352d460 Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Wed, 18 Feb 2026 13:32:20 +0530
Subject: [PATCH 47/58] Moved UUID to int, moved date in logging to single
instacne, moved o(n) to o(1) in logging
---
.../ProxyBridge/ProxyBridgeViewModel.swift | 33 ++++++++++++++-----
1 file changed, 25 insertions(+), 8 deletions(-)
diff --git a/MacOS/ProxyBridge/ProxyBridge/ProxyBridgeViewModel.swift b/MacOS/ProxyBridge/ProxyBridge/ProxyBridgeViewModel.swift
index 6ae2285..43fce49 100644
--- a/MacOS/ProxyBridge/ProxyBridge/ProxyBridgeViewModel.swift
+++ b/MacOS/ProxyBridge/ProxyBridge/ProxyBridgeViewModel.swift
@@ -14,8 +14,20 @@ class ProxyBridgeViewModel: NSObject, ObservableObject {
private(set) var proxyConfig: ProxyConfig?
private let maxLogEntries = 1000
+ // trim to 80% when limit hit to avoid trimming on each entry
+ private let trimToEntries = 800
private let logPollingInterval = 1.0
private let extensionIdentifier = "com.interceptsuite.ProxyBridge.extension"
+ // reuse formatter
+ // saves memory about 2% and speed up the ui
+ private let timestampFormatter: DateFormatter = {
+ let f = DateFormatter()
+ f.dateFormat = "HH:mm:ss"
+ return f
+ }()
+ // removed uuid and use int - memory usage and speed improved due to size
+ private var connectionIdCounter: Int = 0
+ private var activityIdCounter: Int = 0
struct ProxyConfig {
let type: String
@@ -26,7 +38,7 @@ class ProxyBridgeViewModel: NSObject, ObservableObject {
}
struct ConnectionLog: Identifiable {
- let id = UUID()
+ let id: Int
let timestamp: String
let connectionProtocol: String
let process: String
@@ -36,7 +48,7 @@ class ProxyBridgeViewModel: NSObject, ObservableObject {
}
struct ActivityLog: Identifiable {
- let id = UUID()
+ let id: Int
let timestamp: String
let level: String
let message: String
@@ -296,7 +308,9 @@ class ProxyBridgeViewModel: NSObject, ObservableObject {
return
}
+ connectionIdCounter &+= 1
let connectionLog = ConnectionLog(
+ id: connectionIdCounter,
timestamp: getCurrentTimestamp(),
connectionProtocol: proto,
process: process,
@@ -306,8 +320,9 @@ class ProxyBridgeViewModel: NSObject, ObservableObject {
)
connections.append(connectionLog)
+ // Trim in bulk to avoid O(n) shift on every entry at the limit
if connections.count > maxLogEntries {
- connections.removeFirst()
+ connections.removeFirst(connections.count - trimToEntries)
}
}
@@ -318,7 +333,9 @@ class ProxyBridgeViewModel: NSObject, ObservableObject {
return
}
+ activityIdCounter &+= 1
let activityLog = ActivityLog(
+ id: activityIdCounter,
timestamp: timestamp,
level: level,
message: message
@@ -326,7 +343,7 @@ class ProxyBridgeViewModel: NSObject, ObservableObject {
activityLogs.append(activityLog)
if activityLogs.count > maxLogEntries {
- activityLogs.removeFirst()
+ activityLogs.removeFirst(activityLogs.count - trimToEntries)
}
}
@@ -382,7 +399,9 @@ class ProxyBridgeViewModel: NSObject, ObservableObject {
}
private func addLog(_ level: String, _ message: String) {
+ activityIdCounter &+= 1
let log = ActivityLog(
+ id: activityIdCounter,
timestamp: getCurrentTimestamp(),
level: level,
message: message
@@ -390,14 +409,12 @@ class ProxyBridgeViewModel: NSObject, ObservableObject {
activityLogs.append(log)
if activityLogs.count > maxLogEntries {
- activityLogs.removeFirst()
+ activityLogs.removeFirst(activityLogs.count - trimToEntries)
}
}
private func getCurrentTimestamp() -> String {
- let formatter = DateFormatter()
- formatter.dateFormat = "HH:mm:ss"
- return formatter.string(from: Date())
+ return timestampFormatter.string(from: Date())
}
deinit {
From 1aed5cd0e2daf2d6e17310fd43b3b304eb3e8395 Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Wed, 18 Feb 2026 13:39:26 +0530
Subject: [PATCH 48/58] fixed extension unload
---
.../ProxyBridge/ProxyBridgeViewModel.swift | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/MacOS/ProxyBridge/ProxyBridge/ProxyBridgeViewModel.swift b/MacOS/ProxyBridge/ProxyBridge/ProxyBridgeViewModel.swift
index 43fce49..44b938e 100644
--- a/MacOS/ProxyBridge/ProxyBridge/ProxyBridgeViewModel.swift
+++ b/MacOS/ProxyBridge/ProxyBridge/ProxyBridgeViewModel.swift
@@ -127,6 +127,26 @@ class ProxyBridgeViewModel: NSObject, ObservableObject {
}
private func installAndStartProxy() {
+ // Stop any existing tunnel first so macOS replaces the running extension
+ // binary with the newly installed one instead of reusing the old cached process.
+ NETransparentProxyManager.loadAllFromPreferences { [weak self] managers, error in
+ guard let self = self else { return }
+
+ if let existing = managers?.first,
+ let session = existing.connection as? NETunnelProviderSession,
+ session.status != .disconnected && session.status != .invalid {
+ session.stopTunnel()
+ // Brief pause to let the old extension fully terminate
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
+ self.submitExtensionActivationRequest()
+ }
+ } else {
+ self.submitExtensionActivationRequest()
+ }
+ }
+ }
+
+ private func submitExtensionActivationRequest() {
let request = OSSystemExtensionRequest.activationRequest(
forExtensionWithIdentifier: extensionIdentifier,
queue: .main
From 32b426865095da0c759dc03000fe83eaa5152871 Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Wed, 18 Feb 2026 13:43:39 +0530
Subject: [PATCH 49/58] fixed unwanted proxy error logs
---
.../ProxyBridge/extension/AppProxyProvider.swift | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/MacOS/ProxyBridge/extension/AppProxyProvider.swift b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
index 856ca3e..b4f61bf 100644
--- a/MacOS/ProxyBridge/extension/AppProxyProvider.swift
+++ b/MacOS/ProxyBridge/extension/AppProxyProvider.swift
@@ -1208,7 +1208,11 @@ class AppProxyProvider: NETransparentProxyProvider {
private func relayClientToProxy(clientFlow: NEAppProxyTCPFlow, proxyConnection: NWTCPConnection) {
clientFlow.readData { [weak self] data, error in
if let error = error {
- self?.log("Client read error: \(error.localizedDescription)", level: "ERROR")
+ // ignore expected errors
+ let code = (error as NSError).code
+ if code != 57 && code != 54 && code != 89 {
+ self?.log("Client read error: \(error.localizedDescription)", level: "ERROR")
+ }
proxyConnection.cancel()
return
}
@@ -1234,9 +1238,12 @@ class AppProxyProvider: NETransparentProxyProvider {
private func relayProxyToClient(clientFlow: NEAppProxyTCPFlow, proxyConnection: NWTCPConnection) {
proxyConnection.readMinimumLength(1, maximumLength: 65536) { [weak self] data, error in
if let error = error {
- self?.log("Proxy read error: \(error.localizedDescription)", level: "ERROR")
- clientFlow.closeReadWithError(error)
- clientFlow.closeWriteWithError(error)
+ let code = (error as NSError).code
+ if code != 57 && code != 54 && code != 89 {
+ self?.log("Proxy read error: \(error.localizedDescription)", level: "ERROR")
+ }
+ clientFlow.closeReadWithError(nil)
+ clientFlow.closeWriteWithError(nil)
return
}
From a8e7eb630c34d3fc4c6d526892cfd580815a2d62 Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Wed, 18 Feb 2026 13:51:32 +0530
Subject: [PATCH 50/58] fixed arch for mac
---
MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj | 3 ++-
MacOS/ProxyBridge/proxybridge-app.xcconfig | 4 ++++
MacOS/ProxyBridge/proxybridge-ext.xcconfig | 4 ++++
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj b/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
index 2f3500c..31845b5 100644
--- a/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
+++ b/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
@@ -370,7 +370,8 @@
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
- ONLY_ACTIVE_ARCH = YES;
+ ONLY_ACTIVE_ARCH = NO;
+ ARCHS = "arm64 x86_64";
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
};
diff --git a/MacOS/ProxyBridge/proxybridge-app.xcconfig b/MacOS/ProxyBridge/proxybridge-app.xcconfig
index 5d88cfe..7064149 100644
--- a/MacOS/ProxyBridge/proxybridge-app.xcconfig
+++ b/MacOS/ProxyBridge/proxybridge-app.xcconfig
@@ -24,3 +24,7 @@ PROVISIONING_PROFILE_SPECIFIER = ProxyBridge Prod
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO
ENABLE_HARDENED_RUNTIME = YES
+// MARK: - Architecture (Universal Binary: Intel + Apple Silicon)
+ARCHS = arm64 x86_64
+ONLY_ACTIVE_ARCH = NO
+
diff --git a/MacOS/ProxyBridge/proxybridge-ext.xcconfig b/MacOS/ProxyBridge/proxybridge-ext.xcconfig
index f552f14..2c46ec9 100644
--- a/MacOS/ProxyBridge/proxybridge-ext.xcconfig
+++ b/MacOS/ProxyBridge/proxybridge-ext.xcconfig
@@ -23,3 +23,7 @@ PROVISIONING_PROFILE_SPECIFIER = ProxyBridge Extension Prod
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO
ENABLE_HARDENED_RUNTIME = YES
+// MARK: - Architecture (Universal Binary: Intel + Apple Silicon)
+ARCHS = arm64 x86_64
+ONLY_ACTIVE_ARCH = NO
+
From 5cb9e98451f21f10a2625b6c50f99486c93254c0 Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Wed, 18 Feb 2026 16:52:29 +0530
Subject: [PATCH 51/58] update checker with pkg macos
---
MacOS/ProxyBridge/ProxyBridge/UpdateService.swift | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/MacOS/ProxyBridge/ProxyBridge/UpdateService.swift b/MacOS/ProxyBridge/ProxyBridge/UpdateService.swift
index 529937b..ac828f3 100644
--- a/MacOS/ProxyBridge/ProxyBridge/UpdateService.swift
+++ b/MacOS/ProxyBridge/ProxyBridge/UpdateService.swift
@@ -63,10 +63,13 @@ class UpdateService {
asset.name.lowercased().contains("installer"))
}
+ // a macOS pkg installer in the release is valid update
+ let isUpdateAvailable = isNewerVersion(latestVersion, currentVersion) && pkgAsset != nil
+
return VersionInfo(
currentVersion: currentVersion,
latestVersion: release.tagName,
- isUpdateAvailable: isNewerVersion(latestVersion, currentVersion),
+ isUpdateAvailable: isUpdateAvailable,
downloadUrl: pkgAsset?.browserDownloadUrl,
fileName: pkgAsset?.name,
error: nil
From 9ece0f17f7cd7958c804a327119bea2f48bda328 Mon Sep 17 00:00:00 2001
From: AnoF-Cyber
Date: Wed, 18 Feb 2026 16:54:45 +0530
Subject: [PATCH 52/58] Mac 3.2.0
---
MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj | 8 ++++----
MacOS/ProxyBridge/build.sh | 6 +++---
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj b/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
index 31845b5..d761d8c 100644
--- a/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
+++ b/MacOS/ProxyBridge/ProxyBridge.xcodeproj/project.pbxproj
@@ -410,7 +410,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MARKETING_VERSION = 3.1.0;
+ MARKETING_VERSION = 3.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.interceptsuite.ProxyBridge;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -455,7 +455,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MARKETING_VERSION = 3.1.0;
+ MARKETING_VERSION = 3.2.0;
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ProxyBridge Prod";
REGISTER_APP_GROUPS = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -496,7 +496,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
- MARKETING_VERSION = 3.1.0;
+ MARKETING_VERSION = 3.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.interceptsuite.ProxyBridge.extension;
PRODUCT_NAME = "$(inherited)";
REGISTER_APP_GROUPS = YES;
@@ -535,7 +535,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
- MARKETING_VERSION = 3.1.0;
+ MARKETING_VERSION = 3.2.0;
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ProxyBridge Extension Prod";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
diff --git a/MacOS/ProxyBridge/build.sh b/MacOS/ProxyBridge/build.sh
index 94fe978..52628e5 100755
--- a/MacOS/ProxyBridge/build.sh
+++ b/MacOS/ProxyBridge/build.sh
@@ -3,7 +3,7 @@
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-VERSION="3.1.0"
+VERSION="3.2.0"
if [ -f "$SCRIPT_DIR/.env" ]; then
source "$SCRIPT_DIR/.env"
@@ -26,7 +26,7 @@ echo "Creating installer package..."
pkgbuild \
--root build/component \
--identifier com.interceptsuite.ProxyBridge \
- --version 3.1.0 \
+ --version 3.2.0 \
--install-location /Applications \
build/temp.pkg
@@ -45,7 +45,7 @@ cat > build/distribution.xml << 'EOF'
- temp.pkg
+ temp.pkg
EOF
From b68cea4cecce8aa7d4d6a6cd6bf4dc70b214c026 Mon Sep 17 00:00:00 2001
From: Sourav Kalal
Date: Wed, 18 Feb 2026 21:36:12 +0530
Subject: [PATCH 53/58] #75 Fixed Ip range issue
---
Windows/src/ProxyBridge.c | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/Windows/src/ProxyBridge.c b/Windows/src/ProxyBridge.c
index 676f6ab..7a21d5d 100644
--- a/Windows/src/ProxyBridge.c
+++ b/Windows/src/ProxyBridge.c
@@ -826,6 +826,40 @@ static BOOL match_ip_pattern(const char *pattern, UINT32 ip)
if (pattern == NULL || strcmp(pattern, "*") == 0)
return TRUE;
+ // check for IP range
+ char *dash = strchr(pattern, '-');
+ if (dash != NULL)
+ {
+ char start_ip_str[64], end_ip_str[64];
+ size_t start_len = dash - pattern;
+ if (start_len >= sizeof(start_ip_str))
+ return FALSE;
+
+ strncpy_s(start_ip_str, sizeof(start_ip_str), pattern, start_len);
+ start_ip_str[start_len] = '\0';
+ strncpy_s(end_ip_str, sizeof(end_ip_str), dash + 1, _TRUNCATE);
+
+ // parse start and end IPs
+ UINT32 start_ip = 0, end_ip = 0;
+ int s1, s2, s3, s4, e1, e2, e3, e4;
+
+ if (sscanf_s(start_ip_str, "%d.%d.%d.%d", &s1, &s2, &s3, &s4) == 4 &&
+ sscanf_s(end_ip_str, "%d.%d.%d.%d", &e1, &e2, &e3, &e4) == 4)
+ {
+ start_ip = (s1 << 0) | (s2 << 8) | (s3 << 16) | (s4 << 24);
+ end_ip = (e1 << 0) | (e2 << 8) | (e3 << 16) | (e4 << 24);
+
+ // checking as network byte order would be wrong, compare as little-endian UINT32
+ // change to big-endian for proper comparison
+ UINT32 ip_be = ((ip & 0xFF) << 24) | ((ip & 0xFF00) << 8) | ((ip & 0xFF0000) >> 8) | ((ip & 0xFF000000) >> 24);
+ UINT32 start_be = ((start_ip & 0xFF) << 24) | ((start_ip & 0xFF00) << 8) | ((start_ip & 0xFF0000) >> 8) | ((start_ip & 0xFF000000) >> 24);
+ UINT32 end_be = ((end_ip & 0xFF) << 24) | ((end_ip & 0xFF00) << 8) | ((end_ip & 0xFF0000) >> 8) | ((end_ip & 0xFF000000) >> 24);
+
+ return (ip_be >= start_be && ip_be <= end_be);
+ }
+ return FALSE;
+ }
+
// Extract 4 octets from IP (little-endian)
unsigned char ip_octets[4];
ip_octets[0] = (ip >> 0) & 0xFF;
From b2324c12e43bbf105ca1f1d0bbed78006bcb4d93 Mon Sep 17 00:00:00 2001
From: Sourav Kalal
Date: Wed, 18 Feb 2026 21:40:09 +0530
Subject: [PATCH 54/58] added Linux build workflow
---
.github/workflows/build-linux.yml | 104 ++++++++++++++++++++++++++++++
1 file changed, 104 insertions(+)
create mode 100644 .github/workflows/build-linux.yml
diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml
new file mode 100644
index 0000000..13820a8
--- /dev/null
+++ b/.github/workflows/build-linux.yml
@@ -0,0 +1,104 @@
+name: Build Linux
+
+on:
+ push:
+ branches: [ master, dev ]
+ pull_request:
+ branches: [ master, dev ]
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install build dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential \
+ gcc \
+ make \
+ libnetfilter-queue-dev \
+ libnfnetlink-dev \
+ libgtk-3-dev \
+ pkg-config
+ shell: bash
+
+ - name: Verify dependencies
+ run: |
+ echo "=== Checking GCC ==="
+ gcc --version
+ echo ""
+ echo "=== Checking Make ==="
+ make --version
+ echo ""
+ echo "=== Checking pkg-config ==="
+ pkg-config --version
+ echo ""
+ echo "=== Checking GTK3 ==="
+ pkg-config --modversion gtk+-3.0
+ echo ""
+ echo "=== Checking libnetfilter_queue ==="
+ pkg-config --modversion libnetfilter_queue || echo "Package info not available, but headers should be present"
+ shell: bash
+
+ - name: Build project
+ run: |
+ cd Linux
+ chmod +x build.sh
+ ./build.sh
+ shell: bash
+
+ - name: Verify build output
+ run: |
+ echo "=== Build Output ==="
+ ls -lh Linux/output/
+ echo ""
+ if [ -f "Linux/output/libproxybridge.so" ]; then
+ echo "✓ Library built successfully"
+ file Linux/output/libproxybridge.so
+ else
+ echo "✗ Library build failed"
+ exit 1
+ fi
+ echo ""
+ if [ -f "Linux/output/ProxyBridge" ]; then
+ echo "✓ CLI built successfully"
+ file Linux/output/ProxyBridge
+ else
+ echo "✗ CLI build failed"
+ exit 1
+ fi
+ echo ""
+ if [ -f "Linux/output/ProxyBridgeGUI" ]; then
+ echo "✓ GUI built successfully"
+ file Linux/output/ProxyBridgeGUI
+ else
+ echo "⚠ GUI build skipped (GTK3 not available or build failed)"
+ fi
+ shell: bash
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: ProxyBridge-Linux-Build-${{ github.sha }}
+ path: Linux/output/
+ retention-days: 30
+
+ - name: Display build summary
+ run: |
+ echo ""
+ echo "========================================="
+ echo "Build Complete!"
+ echo "========================================="
+ cd Linux/output
+ for file in *; do
+ size=$(du -h "$file" | cut -f1)
+ echo " $file - $size"
+ done
+ echo "========================================="
+ shell: bash
From ac9d1af73d50abad14182b0a755d46ea2ad36816 Mon Sep 17 00:00:00 2001
From: Sourav Kalal
Date: Wed, 18 Feb 2026 22:44:01 +0530
Subject: [PATCH 55/58] Update ProxyBridge version to 3.2.0 and add auto-deploy
script for Linux
---
.github/workflows/release-linux.yml | 129 +++++++++++
Linux/deploy.sh | 346 ++++++++++++++++++++++++++++
Linux/src/ProxyBridge.h | 2 +-
Windows/README.md | 81 +++++--
4 files changed, 532 insertions(+), 26 deletions(-)
create mode 100644 .github/workflows/release-linux.yml
create mode 100644 Linux/deploy.sh
diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml
new file mode 100644
index 0000000..4e2f9b6
--- /dev/null
+++ b/.github/workflows/release-linux.yml
@@ -0,0 +1,129 @@
+name: Release ProxyBridge Linux
+
+on:
+ release:
+ types: [published, created]
+ workflow_dispatch:
+
+jobs:
+ build-and-release:
+ runs-on: ubuntu-latest
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.actor == github.repository_owner)
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install build dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential \
+ gcc \
+ make \
+ libnetfilter-queue-dev \
+ libnfnetlink-dev \
+ libgtk-3-dev \
+ pkg-config
+ shell: bash
+
+ - name: Build project
+ run: |
+ cd Linux
+ chmod +x build.sh
+ ./build.sh
+ shell: bash
+
+ - name: Copy setup script to output
+ run: |
+ echo "Copying setup.sh to output directory..."
+ cp Linux/setup.sh Linux/output/
+ chmod +x Linux/output/setup.sh
+ echo "✓ Setup script copied"
+ shell: bash
+
+ - name: Verify build output
+ run: |
+ echo "=== Build Output ==="
+ ls -lh Linux/output/
+ echo ""
+ if [ -f "Linux/output/libproxybridge.so" ]; then
+ echo "✓ Library built successfully"
+ file Linux/output/libproxybridge.so
+ else
+ echo "✗ Library build failed"
+ exit 1
+ fi
+ echo ""
+ if [ -f "Linux/output/ProxyBridge" ]; then
+ echo "✓ CLI built successfully"
+ file Linux/output/ProxyBridge
+ else
+ echo "✗ CLI build failed"
+ exit 1
+ fi
+ echo ""
+ if [ -f "Linux/output/setup.sh" ]; then
+ echo "✓ Setup script copied"
+ else
+ echo "✗ Setup script missing"
+ exit 1
+ fi
+ shell: bash
+
+ - name: Extract version from tag
+ id: version
+ run: |
+ if [ "${{ github.event_name }}" == "release" ]; then
+ VERSION="${{ github.event.release.tag_name }}"
+ else
+ VERSION="dev-$(date +%Y%m%d-%H%M%S)"
+ fi
+ # Remove 'v' prefix if present
+ VERSION="${VERSION#v}"
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ echo "Version: $VERSION"
+ shell: bash
+
+ - name: Create release archive
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ ARCHIVE_NAME="ProxyBridge-Linux-v${VERSION}.tar.gz"
+
+ echo "Creating archive: $ARCHIVE_NAME"
+ cd Linux/output
+ tar -czf "../$ARCHIVE_NAME" ./*
+
+ echo ""
+ echo "Archive created successfully:"
+ ls -lh "../$ARCHIVE_NAME"
+
+ # Move archive to root for upload
+ mv "../$ARCHIVE_NAME" "../../$ARCHIVE_NAME"
+ shell: bash
+
+ - name: List release files
+ run: |
+ echo ""
+ echo "==================================="
+ echo "Release Files:"
+ echo "==================================="
+ ls -lh ProxyBridge-Linux-*.tar.gz
+
+ echo ""
+ echo "Archive contents:"
+ tar -tzf ProxyBridge-Linux-*.tar.gz
+ shell: bash
+
+ - name: Upload archive to release
+ if: github.event_name == 'release'
+ uses: softprops/action-gh-release@v1
+ with:
+ files: ProxyBridge-Linux-v*.tar.gz
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: ProxyBridge-Linux-Release-${{ steps.version.outputs.version }}
+ path: ProxyBridge-Linux-*.tar.gz
+ retention-days: 90
diff --git a/Linux/deploy.sh b/Linux/deploy.sh
new file mode 100644
index 0000000..d9dd929
--- /dev/null
+++ b/Linux/deploy.sh
@@ -0,0 +1,346 @@
+#!/bin/bash
+
+set -e
+
+GITHUB_REPO="InterceptSuite/ProxyBridge"
+GITHUB_API="https://api.github.com/repos/$GITHUB_REPO/releases/latest"
+TEMP_DIR="/tmp/proxybridge-install-$$"
+
+echo ""
+echo "==================================="
+echo "ProxyBridge Auto-Deploy 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 bash deploy.sh"
+ exit 1
+fi
+
+# Check for required tools
+check_requirements() {
+ echo "Checking required tools..."
+
+ local missing_tools=()
+
+ if ! command -v curl &> /dev/null; then
+ missing_tools+=("curl")
+ fi
+
+ if ! command -v tar &> /dev/null; then
+ missing_tools+=("tar")
+ fi
+
+ if ! command -v jq &> /dev/null; then
+ echo "WARNING: jq not found. Attempting to parse JSON manually."
+ fi
+
+ if [ ${#missing_tools[@]} -gt 0 ]; then
+ echo "ERROR: Missing required tools: ${missing_tools[*]}"
+ echo ""
+ echo "Please install them first:"
+ echo " Debian/Ubuntu: sudo apt install curl tar jq"
+ echo " Fedora: sudo dnf install curl tar jq"
+ echo " Arch: sudo pacman -S curl tar jq"
+ exit 1
+ fi
+
+ echo "All required tools available"
+}
+
+# Download latest release
+download_latest_release() {
+ echo ""
+ echo "Fetching latest release information..."
+
+ # Create temp directory
+ mkdir -p "$TEMP_DIR"
+ cd "$TEMP_DIR"
+
+ # Get release info from GitHub API
+ local api_response
+ api_response=$(curl -sL "$GITHUB_API")
+
+ if [ -z "$api_response" ]; then
+ echo "ERROR: Failed to fetch release information from GitHub"
+ exit 1
+ fi
+
+ # Extract download URL for Linux tar.gz
+ local download_url
+ if command -v jq &> /dev/null; then
+ download_url=$(echo "$api_response" | jq -r '.assets[] | select(.name | test("ProxyBridge-Linux-.*\\.tar\\.gz$")) | .browser_download_url' | head -1)
+ else
+ # Fallback: manual parsing
+ download_url=$(echo "$api_response" | grep -o '"browser_download_url": *"[^"]*ProxyBridge-Linux-[^"]*\.tar\.gz"' | head -1 | sed 's/.*"browser_download_url": *"\([^"]*\)".*/\1/')
+ fi
+
+ if [ -z "$download_url" ]; then
+ echo "ERROR: Could not find Linux release archive in latest release"
+ echo ""
+ echo "Please check if a Linux release is available at:"
+ echo " https://github.com/$GITHUB_REPO/releases/latest"
+ exit 1
+ fi
+
+ # Extract version from URL
+ local filename
+ filename=$(basename "$download_url")
+ echo "Found release: $filename"
+ echo "Download URL: $download_url"
+
+ echo ""
+ echo "Downloading $filename..."
+ if ! curl -L -o "$filename" "$download_url"; then
+ echo "ERROR: Failed to download release archive"
+ exit 1
+ fi
+
+ echo "Download complete"
+
+ # Extract archive
+ echo ""
+ echo "Extracting archive..."
+ if ! tar -xzf "$filename"; then
+ echo "ERROR: Failed to extract archive"
+ exit 1
+ fi
+
+ echo "Extraction complete"
+ echo "Files extracted to: $TEMP_DIR"
+ ls -lh
+}
+
+# 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 ""
+ echo "Detected distribution: $DISTRO"
+}
+
+# Install dependencies based on distribution
+install_dependencies() {
+ echo ""
+ echo "Checking and installing dependencies..."
+
+ # 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 libnfnetlink0 iptables libgtk-3-0
+ ;;
+ fedora)
+ echo "Using dnf package manager..."
+ dnf install -y libnetfilter_queue libnfnetlink iptables gtk3
+ ;;
+ rhel|centos|rocky|almalinux)
+ echo "Using yum package manager..."
+ yum install -y libnetfilter_queue libnfnetlink iptables gtk3
+ ;;
+ arch|manjaro|endeavouros|garuda)
+ echo "Using pacman package manager..."
+ pacman -Sy --noconfirm libnetfilter_queue libnfnetlink iptables gtk3
+ ;;
+ opensuse*|sles)
+ echo "Using zypper package manager..."
+ zypper install -y libnetfilter_queue1 libnfnetlink0 iptables gtk3
+ ;;
+ void)
+ echo "Using xbps package manager..."
+ 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 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
+ 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 extracted directory
+check_files() {
+ echo ""
+ echo "Checking for required files..."
+
+ if [ ! -f "$TEMP_DIR/libproxybridge.so" ]; then
+ echo "ERROR: libproxybridge.so not found in extracted archive"
+ exit 1
+ fi
+
+ if [ ! -f "$TEMP_DIR/ProxyBridge" ]; then
+ echo "ERROR: ProxyBridge binary not found in extracted archive"
+ exit 1
+ fi
+
+ if [ ! -f "$TEMP_DIR/ProxyBridgeGUI" ]; then
+ echo "WARNING: ProxyBridgeGUI binary not found - GUI will not be installed"
+ fi
+
+ echo "All required files present"
+}
+
+# Install files
+install_files() {
+ echo ""
+ echo "Installing ProxyBridge..."
+
+ # Create directories if they don't exist
+ mkdir -p "$LIB_PATH" /usr/local/bin /etc/proxybridge
+ chmod 755 /etc/proxybridge
+
+ # Copy library
+ echo "Installing libproxybridge.so to $LIB_PATH..."
+ cp "$TEMP_DIR/libproxybridge.so" "$LIB_PATH/"
+ chmod 755 "$LIB_PATH/libproxybridge.so"
+
+ # Copy binary
+ echo "Installing ProxyBridge to /usr/local/bin..."
+ cp "$TEMP_DIR/ProxyBridge" /usr/local/bin/
+ chmod 755 /usr/local/bin/ProxyBridge
+
+ if [ -f "$TEMP_DIR/ProxyBridgeGUI" ]; then
+ echo "Installing ProxyBridgeGUI to /usr/local/bin..."
+ cp "$TEMP_DIR/ProxyBridgeGUI" /usr/local/bin/
+ chmod 755 /usr/local/bin/ProxyBridgeGUI
+ fi
+
+ 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
+ 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
+}
+
+# Cleanup temp directory
+cleanup() {
+ echo ""
+ echo "Cleaning up temporary files..."
+ rm -rf "$TEMP_DIR"
+ echo "Cleanup complete"
+}
+
+# Main deployment
+main() {
+ check_requirements
+ download_latest_release
+ detect_distro
+ check_files
+ install_dependencies
+ detect_lib_path
+ install_files
+ update_ldconfig
+ verify_installation
+ cleanup
+
+ echo ""
+ echo "==================================="
+ echo "Installation Complete!"
+ echo "==================================="
+ 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:"
+ echo " sudo ProxyBridge --cleanup"
+ echo ""
+}
+
+# Trap errors and cleanup
+trap 'echo ""; echo "ERROR: Installation failed. Cleaning up..."; rm -rf "$TEMP_DIR"; exit 1' ERR
+
+main
diff --git a/Linux/src/ProxyBridge.h b/Linux/src/ProxyBridge.h
index 023493e..bd80583 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.1.0-Beta"
+#define PROXYBRIDGE_VERSION "3.2.0"
#include
#include
diff --git a/Windows/README.md b/Windows/README.md
index c0ad17a..60a2ebe 100644
--- a/Windows/README.md
+++ b/Windows/README.md
@@ -223,21 +223,22 @@ Usage:
ProxyBridge_CLI [command] [options]
Options:
- --proxy Proxy server URL with optional authentication
- Format: type://ip:port or type://ip:port:username:password
- Examples: socks5://127.0.0.1:1080
- http://proxy.com:8080:myuser:mypass [default: socks5://127.0.0.1:4444]
- --rule Traffic routing rule (multiple values supported, can repeat)
- Format: process:hosts:ports:protocol:action
- process - Process name(s): chrome.exe, chr*.exe, *.exe, or *
- hosts - IP/host(s): *, google.com, 192.168.*.*, or multiple comma-separated
- ports - Port(s): *, 443, 80,443, 80-100, or multiple comma-separated
- protocol - TCP, UDP, or BOTH
- action - PROXY, DIRECT, or BLOCK
- Examples:
- chrome.exe:*:*:TCP:PROXY
- *:*:53:UDP:PROXY
- firefox.exe:*:80,443:TCP:DIRECT
+ --proxy Proxy server URL with optional authentication
+ Format: type://ip:port or type://ip:port:username:password
+ Examples: socks5://127.0.0.1:1080
+ http://proxy.com:8080:myuser:mypass [default: socks5://127.0.0.1:4444]
+ --rule Traffic routing rule (multiple values supported, can repeat)
+ Format: process:hosts:ports:protocol:action
+ process - Process name(s): chrome.exe, chr*.exe, *.exe, or * (use ; for multiple: chrome.exe;firefox.exe)
+ hosts - IP/host(s): *, google.com, 192.168.*.*, or multiple separated by ; or ,
+ ports - Port(s): *, 443, 80;8080, 80-100, or multiple separated by ; or ,
+ protocol - TCP, UDP, or BOTH
+ action - PROXY, DIRECT, or BLOCK
+ Examples:
+ chrome.exe:*:*:TCP:PROXY
+ chrome.exe;firefox.exe:*:*:TCP:PROXY
+ *:*:53:UDP:PROXY
+ firefox.exe:*:80;443:TCP:DIRECT
--rule-file Path to JSON file containing proxy rules
JSON format (same as GUI export):
[{
@@ -249,14 +250,16 @@ Options:
"enabled": true
}]
Example: --rule-file C:\\rules.json []
- --dns-via-proxy Route DNS queries through proxy (default: true) [default: True]
- --verbose Logging verbosity level
- 0 - No logs (default)
- 1 - Show log messages only
- 2 - Show connection events only
- 3 - Show both logs and connections [default: 0]
- --version Show version information
- -?, -h, --help Show help and usage information
+ --dns-via-proxy Route DNS queries through proxy (default: true) [default: True]
+ --localhost-via-proxy Route localhost traffic through proxy (default: false, most proxies block localhost for SSRF prevention, local traffic to remote proxy will cause issues)
+ [default: False]
+ --verbose Logging verbosity level
+ 0 - No logs (default)
+ 1 - Show log messages only
+ 2 - Show connection events only
+ 3 - Show both logs and connections [default: 0]
+ --version Show version information
+ -?, -h, --help Show help and usage information
Commands:
--update Check for updates and download latest version from GitHub
@@ -314,14 +317,42 @@ Commands:
- **DNS Traffic Handling**: DNS traffic on TCP and UDP port 53 is handled separately from proxy rules. Even if you configure rules for port 53, they will be ignored. Instead, DNS routing is controlled by the **DNS via Proxy** option in the Proxy menu (enabled by default). When enabled, all DNS queries are routed through the proxy; when disabled, DNS queries use direct connection.
+- **Localhost Traffic Handling**: Localhost traffic (127.0.0.0/8) requires special handling and is controlled by the **Localhost via Proxy** option in the Proxy menu (disabled by default):
+
+ **Default Behavior (Localhost via Proxy = Disabled):**
+ - ALL localhost traffic automatically uses direct connection
+ - Proxy rules matching 127.x.x.x addresses are automatically overridden to DIRECT
+ - This is the recommended setting for most users
+
+ **Why localhost should stay local:**
+ - **Security**: Most proxy servers reject localhost traffic to prevent SSRF (Server-Side Request Forgery) attacks
+ - **Compatibility**: Many applications run local services that must stay on your machine:
+ - NVIDIA GeForce Experience (local API servers)
+ - Chrome/Edge DevTools (127.0.0.1:9222 debugging protocol)
+ - Development servers (localhost web/database servers)
+ - Inter-process communication (IPC) using TCP/UDP on 127.0.0.1
+ - **Routing Issues**: When localhost traffic goes to a remote proxy:
+ - The proxy server cannot reach services running on YOUR machine
+ - Applications expecting local responses will timeout or fail
+ - Example: `curl http://127.0.0.1:8080` via remote proxy asks the proxy's localhost, not yours
+
+ **When to Enable Localhost via Proxy:**
+ - ✅ Proxy server is running on the same machine (127.0.0.1:1080)
+ - ✅ Security testing: Intercepting localhost traffic in Burp Suite/InterceptSuite
+ - ✅ Your proxy is configured to handle localhost requests properly
+ - ❌ Do NOT enable if proxy is on a different machine/IP address
+
+ **CLI/GUI Options:**
+ - GUI: **Proxy** menu → **Localhost via Proxy** (checkbox)
+ - CLI: `--localhost-via-proxy` flag
+
- **Automatic Direct Routing**: Certain IP addresses and ports automatically use direct connection regardless of proxy rules, though you can still create rules with **DIRECT** (default) or **BLOCK** actions for them:
- - **Localhost addresses** (127.0.0.0/8) - Loopback traffic
- **Broadcast addresses** (255.255.255.255 and x.x.x.255) - Network broadcast
- **Multicast addresses** (224.0.0.0 - 239.255.255.255) - Group communication
- **APIPA addresses** (169.254.0.0/16) - Automatic Private IP Addressing (link-local)
- **DHCP ports** (UDP 67, 68) - Dynamic Host Configuration Protocol
- These addresses and ports are used by system components, network discovery, and essential Windows services. While proxy rules are automatically overridden to DIRECT for these targets, you can still define rules with DIRECT or BLOCK actions to explicitly control or block this traffic. Note that Windows loopback traffic (127.x.x.x) uses a method that bypasses the network interface card (NIC), which currently doesn't support proxy routing due to technical limitations with WinDivert at the network layer.
+ These addresses and ports are used by system components, network discovery, and essential Windows services. While proxy rules are automatically overridden to DIRECT for these targets, you can still define rules with DIRECT or BLOCK actions to explicitly control or block this traffic.
- **UDP Proxy Requirements**: UDP traffic only works when a SOCKS5 proxy is configured. If an HTTP proxy server is configured, ProxyBridge will ignore UDP proxy rules and route UDP traffic as direct connection instead. This limitation does not affect UDP rules with BLOCK or DIRECT actions.
From bb8ca75ab6ed5e1b572fa23e086ead757a5c13c2 Mon Sep 17 00:00:00 2001
From: Sourav Kalal
Date: Wed, 18 Feb 2026 23:06:24 +0530
Subject: [PATCH 56/58] added Linux readme
---
Linux/README.md | 594 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 594 insertions(+)
create mode 100644 Linux/README.md
diff --git a/Linux/README.md b/Linux/README.md
new file mode 100644
index 0000000..90b75a8
--- /dev/null
+++ b/Linux/README.md
@@ -0,0 +1,594 @@
+# ProxyBridge for Linux
+
+Universal proxy client for Linux applications - Route any application through SOCKS5/HTTP proxies.
+
+## Table of Contents
+
+- [Installation](#installation)
+ - [Quick Install (Recommended)](#quick-install-recommended)
+ - [Manual Installation](#manual-installation)
+- [Usage](#usage)
+ - [GUI Application](#gui-application)
+ - [Command Line Interface (CLI)](#command-line-interface-cli)
+ - [Basic Usage](#basic-usage)
+ - [Command Line Options](#command-line-options)
+ - [Rule Format](#rule-format)
+- [Use Cases](#use-cases)
+- [Current Limitations](#current-limitations)
+- [Things to Note](#things-to-note)
+- [How It Works](#how-it-works)
+- [Build from Source](#build-from-source)
+- [Uninstallation](#uninstallation)
+- [License](#license)
+
+## Installation
+
+### Quick Install (Recommended)
+
+**One-command automatic installation:**
+
+```bash
+curl -Lo deploy.sh https://raw.githubusercontent.com/InterceptSuite/ProxyBridge/refs/heads/master/Linux/deploy.sh && sudo bash deploy.sh
+```
+
+This script will:
+- Download the latest release from GitHub automatically
+- Detect your Linux distribution
+- Install all required dependencies (libnetfilter-queue, iptables, GTK3)
+- Install ProxyBridge CLI, GUI, and library to system paths
+- Update library cache (ldconfig)
+- Verify installation
+
+**Supported distributions:**
+- Debian/Ubuntu/Mint/Pop!_OS/Elementary/Zorin/Kali/Raspbian
+- Fedora
+- RHEL/CentOS/Rocky/AlmaLinux
+- Arch/Manjaro/EndeavourOS/Garuda
+- openSUSE/SLES
+- Void Linux
+
+### Manual Installation
+
+**From GitHub Releases:**
+
+1. Download the latest `ProxyBridge-Linux-vX.X.X.tar.gz` from the [Releases](https://github.com/InterceptSuite/ProxyBridge/releases) page
+2. Extract the archive:
+ ```bash
+ tar -xzf ProxyBridge-Linux-vX.X.X.tar.gz
+ cd ProxyBridge-Linux-vX.X.X
+ ```
+3. Run the setup script with root privileges:
+ ```bash
+ sudo ./setup.sh
+ ```
+
+The setup script will:
+- Install runtime dependencies (libnetfilter-queue, iptables, GTK3)
+- Copy binaries to `/usr/local/bin` (ProxyBridge CLI and GUI)
+- Copy library to `/usr/local/lib` (libproxybridge.so)
+- Update library cache
+- Verify installation
+
+**What gets installed:**
+- `/usr/local/bin/ProxyBridge` - Command-line interface
+- `/usr/local/bin/ProxyBridgeGUI` - Graphical interface (if GTK3 available)
+- `/usr/local/lib/libproxybridge.so` - Core library
+- `/etc/proxybridge/` - Configuration directory
+
+## Usage
+
+### GUI Application
+
+**Launch GUI (requires GTK3):**
+
+```bash
+sudo ProxyBridgeGUI
+```
+
+The GTK3-based GUI provides:
+- Proxy configuration (SOCKS5/HTTP with authentication)
+- Visual rule management with process selection
+- Real-time connection monitoring
+- Import/Export rules (JSON format compatible with Windows/macOS)
+- DNS via Proxy toggle
+
+
+### Command Line Interface (CLI)
+
+The CLI provides powerful automation and scripting capabilities with rule-based traffic control.
+
+#### Basic Usage
+
+```bash
+# Help menu
+ProxyBridge --help
+
+# Route curl through SOCKS5 proxy
+sudo ProxyBridge --proxy socks5://127.0.0.1:1080 --rule "curl:*:*:TCP:PROXY"
+
+# Route multiple processes in single rule (semicolon-separated)
+sudo ProxyBridge --proxy http://127.0.0.1:8080 --rule "curl;wget;firefox:*:*:TCP:PROXY"
+
+# Multiple rules with verbose connection logging
+sudo ProxyBridge --proxy http://127.0.0.1:8080 \
+ --rule "curl:*:*:TCP:PROXY" \
+ --rule "wget:*:*:TCP:PROXY" \
+ --verbose 2
+
+# Block specific application from internet access
+sudo ProxyBridge --rule "malware:*:*:BOTH:BLOCK"
+
+# Route specific apps through proxy, block everything else
+sudo ProxyBridge --proxy socks5://127.0.0.1:1080 \
+ --rule "curl:*:*:TCP:PROXY" \
+ --rule "firefox:*:*:TCP:PROXY" \
+ --rule "*:*:*:BOTH:BLOCK"
+
+# Route all through proxy except proxy app itself
+sudo ProxyBridge --proxy socks5://127.0.0.1:1080 \
+ --rule "*:*:*:TCP:PROXY" \
+ --rule "burpsuite:*:*:TCP:DIRECT"
+
+# Target specific IPs and ports
+sudo ProxyBridge --proxy socks5://127.0.0.1:1080 \
+ --rule "curl:192.168.*.*;10.10.*.*:80;443;8080:TCP:PROXY"
+
+# IP range support
+sudo ProxyBridge --proxy socks5://192.168.1.4:4444 \
+ --rule "curl:3.19.110.0-3.19.115.255:*:TCP:PROXY"
+
+# Cleanup after crash (removes iptables rules)
+sudo ProxyBridge --cleanup
+```
+
+#### Command Line Options
+
+```
+ProxyBridge - Universal proxy client for Linux applications
+Version: 4.0-Beta
+
+OPTIONS:
+ --proxy Proxy server URL with optional authentication
+ Format: type://ip:port or type://ip:port:username:password
+ Examples: socks5://127.0.0.1:1080
+ http://proxy.com:8080:myuser:mypass
+ Default: socks5://127.0.0.1:4444
+
+ --rule Traffic routing rule (can be specified multiple times)
+ Format: process:hosts:ports:protocol:action
+ process - Process name(s): curl, cur*, *, or multiple separated by ;
+ hosts - IP/host(s): *, google.com, 192.168.*.*, IP ranges (10.0.0.1-10.0.0.255)
+ ports - Port(s): *, 443, 80;8080, 80-100, or multiple separated by ; or ,
+ protocol - TCP, UDP, or BOTH
+ action - PROXY, DIRECT, or BLOCK
+ Examples:
+ curl:*:*:TCP:PROXY
+ curl;wget:*:*:TCP:PROXY
+ *:*:53:UDP:PROXY
+ firefox:*:80;443:TCP:DIRECT
+ curl:10.10.1.1-10.10.255.255:*:TCP:PROXY
+
+ --dns-via-proxy Route DNS queries through proxy
+ Values: true, false, 1, 0
+ Default: true
+
+ --localhost-via-proxy Enable routing localhost (127.0.0.0/8) traffic through proxy
+ Default: false (disabled)
+
+ Security & Compatibility Warning:
+ - Most proxy servers reject localhost traffic to prevent SSRF attacks
+ - Many applications run local services (databases, APIs, IPC)
+ - Routing localhost traffic to remote proxies breaks these applications
+ - Traffic meant for YOUR machine goes to the proxy server instead
+
+ Only enable if:
+ - Proxy server is on the same machine (127.0.0.1)
+ - You need to intercept localhost traffic for security testing
+ - Your proxy explicitly supports localhost requests
+
+ When disabled (default):
+ - Localhost traffic ALWAYS uses direct connection
+ - Proxy rules matching 127.x.x.x are automatically overridden to DIRECT
+
+ --verbose Logging verbosity level
+ 0 - No logs (default)
+ 1 - Show log messages only
+ 2 - Show connection events only
+ 3 - Show both logs and connections
+
+ --cleanup Cleanup resources (iptables, etc.) from crashed instance
+ Use if ProxyBridge crashed without proper cleanup
+
+ --help, -h Show this help message
+
+EXAMPLES:
+ # Basic usage with default proxy
+ sudo ProxyBridge --rule curl:*:*:TCP:PROXY
+
+ # Multiple rules with custom proxy
+ sudo ProxyBridge --proxy socks5://192.168.1.10:1080 \
+ --rule curl:*:*:TCP:PROXY \
+ --rule wget:*:*:TCP:PROXY \
+ --verbose 2
+
+ # Route DNS through proxy with multiple apps
+ sudo ProxyBridge --proxy socks5://127.0.0.1:1080 \
+ --rule "curl;wget;firefox:*:*:BOTH:PROXY" \
+ --dns-via-proxy true --verbose 3
+
+NOTE:
+ ProxyBridge requires root privileges to use NFQUEUE and iptables.
+ Run with 'sudo' or as root user.
+```
+
+#### Rule Format
+
+**Format:** `process:hosts:ports:protocol:action`
+
+- **process** - Process name(s): `curl`, `curl;wget;firefox`, `fire*`, or `*`
+- **hosts** - Target IP/hostname(s): `*`, `192.168.1.1`, `192.168.*.*`, `10.10.1.1-10.10.255.255`, or `192.168.1.1;10.10.10.10`
+- **ports** - Target port(s): `*`, `443`, `80;443;8080`, `80-8000`, or `80;443;8000-9000`
+- **protocol** - `TCP`, `UDP`, or `BOTH`
+- **action** - `PROXY`, `DIRECT`, or `BLOCK`
+
+**Examples:**
+```bash
+# Single process to proxy
+--rule "curl:*:*:TCP:PROXY"
+
+# Multiple processes in one rule
+--rule "curl;wget;firefox:*:*:TCP:PROXY"
+
+# Wildcard process names
+--rule "fire*:*:*:TCP:PROXY" # Matches firefox, firebird, etc.
+
+# Target specific IPs and ports
+--rule "curl:192.168.*;10.10.*.*:80;443;8080:TCP:PROXY"
+
+# IP range matching
+--rule "curl:3.19.110.0-3.19.115.255:*:TCP:PROXY"
+
+# Allow direct connection (bypass proxy)
+--rule "burpsuite:*:*:TCP:DIRECT"
+```
+
+**Notes:**
+- Process names are case-sensitive on Linux
+- Use `*` as the process name to set a default action for all traffic
+- Press `Ctrl+C` to stop ProxyBridge
+- On crash, use `--cleanup` flag to remove iptables rules
+
+## Use Cases
+
+- Redirect proxy-unaware applications (games, desktop apps) through InterceptSuite/Burp Suite for security testing
+- Route specific applications through Tor, SOCKS5, or HTTP proxies
+- Intercept and analyze traffic from applications that don't support proxy configuration
+- Test application behavior under different network conditions
+- Analyze protocols and communication patterns
+- Penetration testing of Linux applications
+- Network traffic monitoring and debugging
+
+## Current Limitations
+
+- **IPv4 only** (IPv6 not supported)
+- **WSL (Windows Subsystem for Linux) NOT supported** - Neither WSL1 nor WSL2 work with ProxyBridge:
+ - **WSL2**: Kernel does not support `nfnetlink_queue` module
+ - Extension shows as "builtin" but is non-functional at runtime
+ - NFQUEUE handle creation fails
+ - **WSL1**: Uses a translation layer instead of a real Linux kernel
+ - Does not support Netfilter/iptables properly
+ - NFQUEUE is completely unavailable
+ - **Works on:** Native Linux, VirtualBox VMs, VMware, cloud instances (AWS, GCP, Azure), bare-metal servers
+ - **Does NOT work on:** WSL1, WSL2, Docker containers with limited capabilities
+ - **Windows users:** Use the Windows version of ProxyBridge instead - it's specifically designed for Windows and works perfectly
+- **Root privileges required** - NFQUEUE and iptables require root access
+- **GUI requires GTK3** - Command-line interface works without GUI dependencies
+
+## Things to Note
+
+- **DNS Traffic Handling**: DNS traffic on TCP and UDP port 53 is handled separately from proxy rules. Even if you configure rules for port 53, they will be ignored. Instead, DNS routing is controlled by the `--dns-via-proxy` flag (enabled by default). When enabled, all DNS queries are routed through the proxy; when disabled, DNS queries use direct connection.
+
+
+- **Automatic Direct Routing**: Certain IP addresses and ports automatically use direct connection regardless of proxy rules:
+ - **Broadcast addresses** (255.255.255.255 and x.x.x.255) - Network broadcast
+ - **Multicast addresses** (224.0.0.0 - 239.255.255.255) - Group communication
+ - **APIPA addresses** (169.254.0.0/16) - Automatic Private IP Addressing (link-local)
+ - **DHCP ports** (UDP 67, 68) - Dynamic Host Configuration Protocol
+
+ These addresses and ports are used by system components, network discovery, and essential Linux services.
+
+- **UDP Proxy Requirements**: UDP traffic only works when a SOCKS5 proxy is configured. If an HTTP proxy server is configured, ProxyBridge will ignore UDP proxy rules and route UDP traffic as direct connection instead. This limitation does not affect UDP rules with BLOCK or DIRECT actions.
+
+ **Important UDP Considerations**:
+ - Configuring a SOCKS5 proxy does not guarantee UDP will work. Most SOCKS5 proxies do not support UDP traffic, including SSH SOCKS5 proxies.
+ - The SOCKS5 proxy must support UDP ASSOCIATE command. If ProxyBridge fails to establish a UDP association with the SOCKS5 proxy, packets will fail to connect.
+ - Many UDP applications use HTTP/3 and DTLS protocols. Even if your SOCKS5 proxy supports UDP ASSOCIATE, ensure it can handle DTLS and HTTP/3 UDP traffic, as they require separate handling beyond raw UDP packets.
+ - **Testing UDP/HTTP3/DTLS Support**: If you need to test UDP, HTTP/3, and DTLS support with a SOCKS5 proxy, try [Nexus Proxy](https://github.com/InterceptSuite/nexus-proxy) - a proxy application created specifically to test ProxyBridge with advanced UDP protocols.
+
+- **Root Privileges**: ProxyBridge requires root access to:
+ - Create NFQUEUE handles for packet interception
+ - Add/remove iptables rules in mangle and nat tables
+ - Listen on relay ports (34010 for TCP, 34011 for UDP)
+
+ Always run with `sudo` or as root user.
+
+- **Process Name Matching**: Linux process names are case-sensitive. Use exact process names or wildcard patterns:
+ - Exact: `firefox` matches only "firefox"
+ - Wildcard: `fire*` matches "firefox", "firebird", "firestorm", etc.
+ - All: `*` matches all processes
+
+## How It Works
+
+ProxyBridge uses Linux Netfilter NFQUEUE to intercept TCP/UDP packets and applies user-defined rules to route traffic through proxies.
+
+**Case 1: Packet does not match any rules**
+
+```
+Application → TCP/UDP Packet → NFQUEUE → ProxyBridge
+ ↓
+ [No Match or DIRECT]
+ ↓
+ Packet Verdict: ACCEPT
+ ↓
+ Direct Connection → Internet
+```
+
+**Case 2: Packet matches proxy rule**
+
+```
+Application → TCP/UDP Packet → NFQUEUE → ProxyBridge
+ ↓
+ [PROXY Rule Match]
+ ↓
+ Packet Verdict: ACCEPT + Mark
+ ↓
+ iptables NAT REDIRECT
+ ↓
+ Relay Server (34010/34011) ← Packet
+ ↓
+ [Store Original Destination]
+ ↓
+ SOCKS5/HTTP Protocol Conversion
+ ↓
+ Proxy Server (Burp Suite/InterceptSuite)
+ ↓
+ Forward to Original Destination
+ ↓
+ Internet
+ ↓
+ Response Returns
+ ↓
+ Relay Server
+ ↓
+ [Restore Original Source IP/Port]
+ ↓
+ Application Receives Response
+```
+
+**Detailed Traffic Flow:**
+
+1. **Applications Generate Traffic** - User-mode applications (curl, wget, firefox, games) create TCP/UDP packets
+
+2. **Kernel Interception** - iptables rules in the mangle table send packets to NFQUEUE:
+ ```bash
+ iptables -t mangle -A OUTPUT -p tcp -j NFQUEUE --queue-num 0
+ iptables -t mangle -A OUTPUT -p udp -j NFQUEUE --queue-num 0
+ ```
+
+3. **NFQUEUE Delivery** - libnetfilter_queue delivers packets to ProxyBridge in userspace
+
+4. **Rule Evaluation** - ProxyBridge inspects each packet and applies configured rules:
+ - **BLOCK** → Packet verdict: DROP (no network access)
+ - **DIRECT** → Packet verdict: ACCEPT (direct connection)
+ - **NO MATCH** → Packet verdict: ACCEPT (direct connection)
+ - **PROXY** → Packet verdict: ACCEPT + set mark (1 for TCP, 2 for UDP)
+
+5. **NAT Redirection** - For PROXY-matched packets, iptables NAT rules redirect marked packets:
+ ```bash
+ iptables -t nat -A OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010
+ iptables -t nat -A OUTPUT -p udp -m mark --mark 2 -j REDIRECT --to-port 34011
+ ```
+
+6. **Relay Servers** - Local relay servers (34010 for TCP, 34011 for UDP):
+ - Intercept redirected packets using getsockopt(SO_ORIGINAL_DST)
+ - Store original destination IP and port
+ - Convert raw TCP/UDP to SOCKS5/HTTP proxy protocol
+ - Perform proxy authentication if configured
+ - Forward to configured proxy server
+
+7. **Proxy Forwarding** - Proxy server (Burp Suite/InterceptSuite) forwards traffic to the original destination
+
+8. **Response Handling** - Return traffic flows back through relay servers, which restore original source IP/port
+
+**Key Technical Points:**
+
+- **NFQUEUE vs WinDivert**: Linux uses Netfilter NFQUEUE (kernel feature) instead of WinDivert (Windows kernel driver)
+
+- **Why NFQUEUE Instead of eBPF**: While eBPF is the modern approach for Linux networking tasks and offers better performance, ProxyBridge uses NFQUEUE due to fundamental limitations discovered during development:
+ - **Original Plan**: ProxyBridge 3.1.0 for Linux was initially developed using eBPF after weeks of implementation
+ - **eBPF Memory Limitations**: eBPF provides limited memory space, which proved insufficient for ProxyBridge's feature set:
+ - ProxyBridge supports multiple complex proxy rules with wildcard matching, IP ranges, and process patterns
+ - Storing and evaluating these rules within eBPF's memory constraints was not feasible
+ - Alternative workarounds added excessive latency (200-500ms+ per packet)
+ - **Performance Requirements**: ProxyBridge's core design goals couldn't be met with eBPF:
+ - Work efficiently with minimal memory usage under high load
+ - Handle high traffic volumes (10,000+ concurrent connections)
+ - **Network speed impact must be ≤2-5% for proxied traffic only**
+ - **Zero performance impact on direct (non-proxied) traffic**
+ - eBPF implementation with all required features caused 15-30% slowdown on all traffic
+ - **NFQUEUE Advantages**:
+ - Userspace processing allows unlimited memory for complex rule evaluation
+ - Selective packet inspection - only examines packets, doesn't slow down uninspected traffic
+ - Mature, stable kernel interface available on all Linux distributions
+ - Lower latency than eBPF workarounds when handling complex rule sets
+
+ **Verdict**: NFQUEUE provides the right balance of flexibility, performance, and compatibility for ProxyBridge's requirements. While eBPF excels for simple packet filtering, ProxyBridge's advanced rule engine and protocol conversion needs are better served by NFQUEUE's userspace processing model.
+
+- **Packet Marking**: ProxyBridge marks packets (mark=1 for TCP, mark=2 for UDP) instead of modifying destination
+- **iptables Integration**: Uses mangle table (pre-routing processing) + nat table (port redirection)
+- **Transparent Redirection**: Applications remain completely unaware of proxying
+- **SO_ORIGINAL_DST**: Socket option retrieves original destination after NAT redirect
+- **Multi-threaded**: Separate threads for packet processing, TCP relay, UDP relay, and connection cleanup
+
+**Architecture Advantages:**
+
+- No kernel modules required (uses built-in Netfilter)
+- Works on any Linux distribution with iptables and NFQUEUE support
+- Leverages proven kernel infrastructure (Netfilter/iptables)
+- Separate packet marking prevents packet modification in NFQUEUE
+- Clean separation: NFQUEUE for inspection, iptables for redirection
+
+## Build from Source
+
+### Requirements
+
+- Linux kernel with NFQUEUE support (virtually all modern distributions)
+- GCC compiler
+- Make
+- Development libraries:
+ - libnetfilter-queue-dev (Debian/Ubuntu) or libnetfilter_queue-devel (Fedora/RHEL)
+ - libnfnetlink-dev (Debian/Ubuntu) or libnfnetlink-devel (Fedora/RHEL)
+ - libgtk-3-dev (optional, for GUI) or gtk3-devel (Fedora/RHEL)
+ - pkg-config
+
+### Building
+
+1. **Install build dependencies:**
+
+ **Debian/Ubuntu/Mint:**
+ ```bash
+ sudo apt-get update
+ sudo apt-get install build-essential gcc make \
+ libnetfilter-queue-dev libnfnetlink-dev \
+ libgtk-3-dev pkg-config
+ ```
+
+ **Fedora:**
+ ```bash
+ sudo dnf install gcc make \
+ libnetfilter_queue-devel libnfnetlink-devel \
+ gtk3-devel pkg-config
+ ```
+
+ **Arch/Manjaro:**
+ ```bash
+ sudo pacman -S base-devel gcc make \
+ libnetfilter_queue libnfnetlink \
+ gtk3 pkg-config
+ ```
+
+2. **Clone or download ProxyBridge source code:**
+ ```bash
+ git clone https://github.com/InterceptSuite/ProxyBridge.git
+ cd ProxyBridge/Linux
+ ```
+
+3. **Build using the build script:**
+ ```bash
+ chmod +x build.sh
+ ./build.sh
+ ```
+
+ This will compile:
+ - `libproxybridge.so` - Core library
+ - `ProxyBridge` - CLI application
+ - `ProxyBridgeGUI` - GUI application (if GTK3 is available)
+
+4. **Install to system paths:**
+ ```bash
+ sudo ./setup.sh
+ ```
+
+ Or manually copy binaries:
+ ```bash
+ sudo cp output/libproxybridge.so /usr/local/lib/
+ sudo cp output/ProxyBridge /usr/local/bin/
+ sudo cp output/ProxyBridgeGUI /usr/local/bin/ # If GUI was built
+ sudo ldconfig
+ ```
+
+### Build Output
+
+After successful build, binaries will be in the `output/` directory:
+- `output/libproxybridge.so` - Shared library
+- `output/ProxyBridge` - CLI binary
+- `output/ProxyBridgeGUI` - GUI binary (if GTK3 available)
+
+### Manual Compilation
+
+**Library:**
+```bash
+cd src
+gcc -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 \
+ -c ProxyBridge.c -o ProxyBridge.o
+
+gcc -shared -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s \
+ -o libproxybridge.so ProxyBridge.o \
+ -lpthread -lnetfilter_queue -lnfnetlink
+```
+
+**CLI:**
+```bash
+cd cli
+gcc -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 \
+ -c main.c -o main.o
+
+gcc -o ProxyBridge main.o \
+ -L../src -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s \
+ -Wl,-rpath,/usr/local/lib \
+ -lproxybridge -lpthread
+```
+
+## Uninstallation
+
+To remove ProxyBridge from your system:
+
+```bash
+# Remove binaries
+sudo rm -f /usr/local/bin/ProxyBridge
+sudo rm -f /usr/local/bin/ProxyBridgeGUI
+
+# Remove library
+sudo rm -f /usr/local/lib/libproxybridge.so
+
+# Remove configuration
+sudo rm -rf /etc/proxybridge
+
+# Update library cache
+sudo ldconfig
+
+# Remove ld.so.conf entry (if exists)
+sudo rm -f /etc/ld.so.conf.d/proxybridge.conf
+sudo ldconfig
+```
+
+**Cleanup after crash:**
+
+If ProxyBridge crashed and left iptables rules:
+```bash
+sudo ProxyBridge --cleanup
+```
+
+Or manually remove iptables rules:
+```bash
+sudo iptables -t mangle -D OUTPUT -p tcp -j NFQUEUE --queue-num 0
+sudo iptables -t mangle -D OUTPUT -p udp -j NFQUEUE --queue-num 0
+sudo iptables -t nat -D OUTPUT -p tcp -m mark --mark 1 -j REDIRECT --to-port 34010
+sudo iptables -t nat -D OUTPUT -p udp -m mark --mark 2 -j REDIRECT --to-port 34011
+```
+
+## License
+
+MIT License - See LICENSE file for details
+
+---
+
+**Author:** Sourav Kalal / InterceptSuite
+**GitHub:** https://github.com/InterceptSuite/ProxyBridge
+**Documentation:** https://github.com/InterceptSuite/ProxyBridge/tree/master/Linux
From 0297297f65bc92a8697bdfc5d5f153d086af87a1 Mon Sep 17 00:00:00 2001
From: anof-cyber
Date: Wed, 18 Feb 2026 23:29:05 +0530
Subject: [PATCH 57/58] updated readme
---
Linux/README.md | 42 +++++++++++++++++++-----------------------
1 file changed, 19 insertions(+), 23 deletions(-)
diff --git a/Linux/README.md b/Linux/README.md
index 90b75a8..11ef83f 100644
--- a/Linux/README.md
+++ b/Linux/README.md
@@ -144,8 +144,22 @@ sudo ProxyBridge --cleanup
#### Command Line Options
```
-ProxyBridge - Universal proxy client for Linux applications
-Version: 4.0-Beta
+sudo ProxyBridge --help
+
+ ____ ____ _ _
+ | _ \ _ __ _____ ___ _ | __ ) _ __(_) __| | __ _ ___
+ | |_) | '__/ _ \ \/ / | | | | _ \| '__| |/ _` |/ _` |/ _ \
+ | __/| | | (_) > <| |_| | | |_) | | | | (_| | (_| | __/
+ |_| |_| \___/_/\_\\__, | |____/|_| |_|\__,_|\__, |\___|
+ |___/ |___/ V3.2.0
+
+ Universal proxy client for Linux applications
+
+ Author: Sourav Kalal/InterceptSuite
+ GitHub: https://github.com/InterceptSuite/ProxyBridge
+
+USAGE:
+ ProxyBridge [OPTIONS]
OPTIONS:
--proxy Proxy server URL with optional authentication
@@ -157,7 +171,7 @@ OPTIONS:
--rule Traffic routing rule (can be specified multiple times)
Format: process:hosts:ports:protocol:action
process - Process name(s): curl, cur*, *, or multiple separated by ;
- hosts - IP/host(s): *, google.com, 192.168.*.*, IP ranges (10.0.0.1-10.0.0.255)
+ hosts - IP/host(s): *, google.com, 192.168.*.*, or multiple separated by ; or ,
ports - Port(s): *, 443, 80;8080, 80-100, or multiple separated by ; or ,
protocol - TCP, UDP, or BOTH
action - PROXY, DIRECT, or BLOCK
@@ -166,30 +180,11 @@ OPTIONS:
curl;wget:*:*:TCP:PROXY
*:*:53:UDP:PROXY
firefox:*:80;443:TCP:DIRECT
- curl:10.10.1.1-10.10.255.255:*:TCP:PROXY
--dns-via-proxy Route DNS queries through proxy
Values: true, false, 1, 0
Default: true
- --localhost-via-proxy Enable routing localhost (127.0.0.0/8) traffic through proxy
- Default: false (disabled)
-
- Security & Compatibility Warning:
- - Most proxy servers reject localhost traffic to prevent SSRF attacks
- - Many applications run local services (databases, APIs, IPC)
- - Routing localhost traffic to remote proxies breaks these applications
- - Traffic meant for YOUR machine goes to the proxy server instead
-
- Only enable if:
- - Proxy server is on the same machine (127.0.0.1)
- - You need to intercept localhost traffic for security testing
- - Your proxy explicitly supports localhost requests
-
- When disabled (default):
- - Localhost traffic ALWAYS uses direct connection
- - Proxy rules matching 127.x.x.x are automatically overridden to DIRECT
-
--verbose Logging verbosity level
0 - No logs (default)
1 - Show log messages only
@@ -217,8 +212,9 @@ EXAMPLES:
--dns-via-proxy true --verbose 3
NOTE:
- ProxyBridge requires root privileges to use NFQUEUE and iptables.
+ ProxyBridge requires root privileges to use nfqueue.
Run with 'sudo' or as root user.
+
```
#### Rule Format
From 489b90139ea334240b39f425286eba2b0e5f7230 Mon Sep 17 00:00:00 2001
From: Sourav Kalal
Date: Wed, 18 Feb 2026 23:44:06 +0530
Subject: [PATCH 58/58] updated Readme , added Linux Screenshot
---
README.md | 59 +++++++++++++++++++++++++++++++---
img/ProxyBridge-linux.png | Bin 0 -> 396035 bytes
img/ProxyBridge_CLI-linux.png | Bin 0 -> 4319937 bytes
img/proxy-rule-linux.png | Bin 0 -> 60801 bytes
img/proxy-rule2-linux.png | Bin 0 -> 64262 bytes
img/proxy-setting-linux.png | Bin 0 -> 61104 bytes
6 files changed, 54 insertions(+), 5 deletions(-)
create mode 100644 img/ProxyBridge-linux.png
create mode 100644 img/ProxyBridge_CLI-linux.png
create mode 100644 img/proxy-rule-linux.png
create mode 100644 img/proxy-rule2-linux.png
create mode 100644 img/proxy-setting-linux.png
diff --git a/README.md b/README.md
index a5e03ed..cdea538 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,10 @@
-ProxyBridge is a lightweight, open-source universal proxy client (Proxifier alternative) that provides transparent proxy routing for applications on **Windows** and **macOS**. It redirects TCP and UDP traffic from specific processes through SOCKS5 or HTTP proxies, with the ability to route, block, or allow traffic on a per-application basis. ProxyBridge fully supports both TCP and UDP proxy routing and works at the system level, making it compatible with proxy-unaware applications without requiring any configuration changes.
+ProxyBridge is a lightweight, open-source universal proxy client (Proxifier alternative) that provides transparent proxy routing for applications on **Windows**, **macOS**, and **Linux**. It redirects TCP and UDP traffic from specific processes through SOCKS5 or HTTP proxies, with the ability to route, block, or allow traffic on a per-application basis. ProxyBridge fully supports both TCP and UDP proxy routing and works at the system level, making it compatible with proxy-unaware applications without requiring any configuration changes.
-🚀 **Need advanced traffic analysis?** Check out [**InterceptSuite**](https://github.com/InterceptSuite/InterceptSuite) - our comprehensive MITM proxy for analyzing TLS, TCP, UDP, DTLS traffic. Perfect for security testing, network debugging, and system administration!
+> [!TIP]
+> **Need advanced traffic analysis?** Check out [**InterceptSuite**](https://github.com/InterceptSuite/InterceptSuite) - our comprehensive MITM proxy for analyzing TLS, TCP, UDP, DTLS traffic. Perfect for security testing, network debugging, and system administration!
## Table of Contents
@@ -28,7 +29,7 @@ ProxyBridge is a lightweight, open-source universal proxy client (Proxifier alte
## Features
-- **Cross-platform** - Available for Windows and macOS
+- **Cross-platform** - Available for Windows, macOS and Linux
- **Dual interface** - Feature-rich GUI and powerful CLI for all use cases
- **Process-based traffic control** - Route, block, or allow traffic for specific applications
- **Universal compatibility** - Works with proxy-unaware applications
@@ -54,14 +55,14 @@ ProxyBridge is a lightweight, open-source universal proxy client (Proxifier alte
> - Official Website: [https://interceptsuite.com/download/proxybridge](https://interceptsuite.com/download/proxybridge)
>
> If you prefer not to use prebuilt binaries, you may safely build ProxyBridge yourself by following the **Contribution Guide** and compiling directly from the **official source code**.
->
+>
> ProxyBridge does not communicate with any external servers except the GitHub API for update checks (triggered only on app launch or manual update checks);
## Platform Documentation
-ProxyBridge is available for both Windows and macOS, with platform-specific implementations:
+ProxyBridge is available for Windows, macOS, and Linux, with platform-specific implementations:
### 📘 Windows
- **[View Full Windows Documentation](Windows/README.md)**
@@ -78,6 +79,15 @@ ProxyBridge is available for both Windows and macOS, with platform-specific impl
- **Requirements**: macOS 13.0 (Ventura) or later, Apple Silicon (ARM) or Intel
- **GUI**: Native SwiftUI interface
+### 📙 Linux
+- **[View Full Linux Documentation](Linux/README.md)**
+- **Technology**: Netfilter NFQUEUE for kernel-level packet interception
+- **Distribution**: TAR.GZ archive or one-command install from [Releases](https://github.com/InterceptSuite/ProxyBridge/releases)
+- **Requirements**: Linux kernel with NFQUEUE support, root privileges (not compatible with WSL1/WSL2)
+- **GUI**: GTK3-based interface (optional)
+- **CLI**: Full-featured command-line tool with rule support
+- **Quick Install**: `curl -Lo deploy.sh https://raw.githubusercontent.com/InterceptSuite/ProxyBridge/refs/heads/master/Linux/deploy.sh && sudo bash deploy.sh`
+
## Screenshots
### macOS
@@ -142,6 +152,42 @@ ProxyBridge is available for both Windows and macOS, with platform-specific impl
ProxyBridge CLI Interface
+### Linux
+
+#### GUI
+
+
+
+
+ ProxyBridge GUI - Main Interface
+
+
+
+
+
+ Proxy Settings Configuration
+
+
+
+
+
+ Proxy Rules Management
+
+
+
+
+
+ Add/Edit Proxy Rule
+
+
+#### CLI
+
+
+
+
+ ProxyBridge CLI Interface
+
+
## Use Cases
- Redirect proxy-unaware applications (games, desktop apps) through InterceptSuite/Burp Suite for security testing
@@ -170,3 +216,6 @@ The Windows GUI is built using [Avalonia UI](https://avaloniaui.net/) - a cross-
**macOS Implementation:**
Built using Apple's Network Extension framework for transparent proxy capabilities on macOS.
+
+**Linux Implementation:**
+Built using Linux Netfilter NFQUEUE for kernel-level packet interception and iptables for traffic redirection. The GUI uses GTK3 for native Linux desktop integration.
diff --git a/img/ProxyBridge-linux.png b/img/ProxyBridge-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..7171e4bc2f56889db5ac8fe59e5602fe244084bb
GIT binary patch
literal 396035
zcmb@ubzGEP*EWoafPjLCk}9R7(v1=#Idn51-9zU9q97t7BGM&N(j7yGfV6ZE-8C?D
z4BsB(zMuDx_j~?%$Dh|F!@2jl_S$P5>sV_?kdmS_AwC5@4h{~X%nL~s9Gok1I5?MT
zu3ZG*aBC%-f&X1_Qjr$N$?K+C0Y6+ef2QyZ2d6NU;PmAc@H3wM3vDMHoZAf8zZYaw
z=uzNH5@#tbXH}S)v)fBYQyghWQ&T%93z#!h{TgU;9w;OEOwAp-erBqrXZelYXRG+!
zJjrPvNjKTgnV={rj;JC~mzvQ~3aAolcr9mN?tl3;Daq{SCXt_3MuzEFhBtiwWUuDE
z?6pVSLt(YPa`ZJKU_=szrAT!RpTTDPh&y$7}iL
z`{i|Kdld!^!5hPMm_6SMKvo=_`i{A}^OHI@%T2zzqb}?GntkwS-F6EltoCS87gn>k
zKMp&`yxfvsn#!3y^P0QOrn8If`)Dz)?rZ~Q;I&a&KIP*z(-;ukqOB3FMGAcyE++fC
zYaDlNEysh#00MGWE!1Kh46_Jcv6t_AHU?e;+N(-n2I9|Q8l#w+1Eg+&VZ%6(cCn%y
z>A9BRhSV;x=4YVK^$@(l6_peawk!-qUh7A`xzI(|1Od7L1PxcqP
zxy2TT6^YAShgkan^Jo^M-{UFt
zS9V*f+Rgt2LvGxqtj9_v(3S5;j0{41@(O&V4G=mCT%42@IO67KYb8p!A%j`
z3Y0Ju=1e2`;AUuRv82YDzS!McokpWYroaun*K*5!&ejYd&I5c*r$8W`e1bpC>(nG4
zgf$qVj!s(%vTr{Kd)KqXb>}+h()(3wz-t9FnZvr{!F(U!KO9u%iif||_$u?A*?1c^Qctb6Ow$6V8}^%*`0FTQ)ja%l5@A`PKppIUvF4
z*7**5l7SaR|LE>!zkk1>@43FFrlx^`fx5c7o?bQl$PosE6>145iPej7d3l|D`SPW&
zuWxK@?CsmP;J-yhMW?5y0|RQ|ZQQCFNu7OfX-P@B9zQO4+4MFwm7X_F#5VL^0R-a=c
zyXy>x@XiwgwzTuHVJrMcb2GKDJ8CXe3=KKR+X!*iOD>{ot^N{
zpB3Zzw-=%eFuVN_u~Ssjuz~N{b}MaYNJxT^TV6^E9V6p&dJ={41>}3BhtA;R;u7(K
zeyC$pq7fIz-SkF~BoJIx%bUcg&^?{08U4hofr^^?Y;ksZ*`j8&!d&NZ4RmW}W@dUC
z%zDo4Of_a)TwG#e;u;_5Wqo~WO3JnA=^JEZ>PkuljtjCd*jCWH
zJT>N~#>V!J4q*M8v$NtE{Y>rf^^rpG+8;lD%+Jq9uxQcYf39(eJAo^PAVNfnJ!InXRm?ftTYG5U8rEiin6vJqR=9p`@g|`etT!
zmV}nJ&T*lAb#)bZ&O%32RaF(i<<~(j0B*GDu<T4rdJC;
z3Z(VncbJtF7k@4JATvK-%yoSPSSD&EBgyA%Zy0NXVRX_YBqT5BKR>1M|||*7mYc%Z68oiD_wPp(Du`;{ky{
zBqe`Tx~x5ymKHldMURc?kdu?k;a$3P3AoYuZiX1Op!35gPr^DD6u%(y^C8)TAm%&>
zdl=OLa5Ya4u>)qHw7i@$kQVZ<^D&`BINHZTQ;53<589baHYLOfbD?0EHrf7u8~p*Y?(i
zLBqHH{x%a8+vpxKG5Ddqtn7PvMmYrq5y$zT0Evq|(2Jiw-H^iz4-bbViJk%2A|fKv
zk+q0GfuI`P?C9bG?4|+?);!5u0}2)GRrp#_aftO#0MF;BK(Tlcul?PXej(TO?|FG(
zDpW~*EG(9P{rV+M>%9x6-FvrJdZ{Og7R>j0aSsM_PzO;wyy$mNOza#?gn`e|0x($5
zqn!nbKq7r$)}g+qbKqTNARK-Ddar}1rlv+$R~I%}iJhzoV1w(U#XzZtI~^S7hpl3H
z(3-LC?vKw?CQs1!Ow{rm79S$d{yTnA53Uz$AazlV`{r(f)ZUlvd2H4J{xb
z5ZUQF`vXsIQ8RlGcq!j+;B-Sa^8?QFYE+8>zQ_;e8^9m{DuD9`(jTdUf`b941BCy$
z!vM20YM!L7p^**(COy6X+qbLEfPvs^XJclDc?HS
zR4oWb9fcEB&GK<<2)*
z+1asvQ3v?$;-VQ%sPuy{#&-C|Sn1~G=Frg4V74Y`p~HBS-($z*#fzVkw1)t|x9ZL@
zNx+qAy-!qBRly9Wq@)0K#>g+u`$OnS?p}W`Bhv2>8(s$g`@vPxetL`wmp|`npXA`Xin6
zM>P$N#l=NlK0aCi;~+5;0ygL9$Qjz|wTuS3HGd=dpo^2~G@LZJIZ{kz2sg1K{)$(l
z|M@yTF9L$uo@)i+(&O}S+jx%kY~MWS0L>ch(&&q}-R%SPI=V|z2U+C?)6pd9@)?dJEcw>&1CMPF-e0)gp&WCb!NhKz1ZEUnyBa@P*a&^mn0ObJcK0G`u&|;O-
zFaS|~3bS7h(4;6Qr?U??w87Bb)00NTa~Esc*qjq+TF7nFX0j4+&G0sp4);31pSA$c
z&d#p|G3ZdCcX+Oor+7qC5Ky~T>Bd7H_oY?}YA%N-%(~jG2
zZf=a>mf9W#jT9=}1gWtgnA6CRPWcD3A3J{AeM{
z*w{E*BiFOn?8#*ucXjQSFI|b6N&Z@_x366;UK+IQ0?E7
zgY)CrV2+_2ZBi;Ko0`5`=tm=8{LdG4{}YXlz^tACTLg3njXBx!-DzXOGB#}5z0e*3
zOdN1e)kZ*UGcz**1oQ&;t~72X%GHpgCt=I)$<59_0nM=Ke`!g{;~4o_{;(G%fH=*y
zhMv#So*yoWog)C=ynAU=na7lzE~wO}>Akwa3u@T3)REf8gV-av(tNe5&FSF42#c7DvQHP%a=tyf!1K46!pyppclD7yxjAMAX=M1P})B
zhHXv$%Mn0Zz=^`*J2Iy>e0`1k}9>|_?X7~lcog&?(&qr~iQj5Cd)EV*^`^(TqNP9Gx(
zlf;vkodr8?oMFQ{&=wj{HbCVDt@eIUu9WK7`uJdgmkJ9DSHGM9k#ZARV;$2omDL#;
zv(L&gpQ7{-Kj2$c)d@Dq!W<9R0X)??zS7Ax1Mpr`b2x;O53|7Gi||03L7_AO;vg|s
zC9MTPR8bKN$!wFiu^cs@;N$!EW96`^d0sdlUpcnX2F&*aWR^mLf)_7dgu~(T2Z*Yw
zaloDM!H9dt;?XSvAf4!H1{nk3_lsPxiIPRu5mLrebIpRFn0-dG?xbi-n33z5wm9a0
z>Zv