commit 98feed1934bc2503d597f78d9b66090004765920 Author: Joerg Dorgeist Date: Wed Jan 14 10:15:44 2026 +0100 Works diff --git a/app.html b/app.html new file mode 100644 index 0000000..0516f46 --- /dev/null +++ b/app.html @@ -0,0 +1,14 @@ + + + + Test Chrome App + + +

Chrome App Test

+

If you see this, the Chrome App loaded successfully!

+

Check the console for TCP server status.

+ + + diff --git a/background.js b/background.js new file mode 100644 index 0000000..82fff8b --- /dev/null +++ b/background.js @@ -0,0 +1,448 @@ +/** + * Chrome App - SOCKS5 Proxy with Bidirectional Tunneling + * Supports both HTTP and HTTPS through true SOCKS5 tunneling + */ + +// Configuration +var PROXY_HOST = '127.0.0.1'; +var PROXY_PORT = 1080; + +// TCP Server Management +var serverSocketId = null; +var isListening = false; + +// SOCKS5 Connection States +var ConnectionState = { + GREETING: 'greeting', + REQUEST: 'request', + TUNNEL: 'tunnel', // NEW: Tunneling state + CLOSED: 'closed' +}; + +// Track connections: clientSocketId -> { state, buffer, destinationSocketId } +var connections = {}; + +// Track reverse mapping: destinationSocketId -> clientSocketId +var reverseConnections = {}; + +// ============================================================================ +// TCP SERVER MANAGEMENT +// ============================================================================ + +function createServer(host, port, callback) { + chrome.sockets.tcpServer.create({}, function(createInfo) { + if (chrome.runtime.lastError) { + callback(chrome.runtime.lastError, null); + return; + } + + serverSocketId = createInfo.socketId; + console.log('[TCP Server] Created server socket: ' + serverSocketId); + + chrome.sockets.tcpServer.listen(serverSocketId, host, port, function(result) { + if (result < 0) { + callback(new Error('Failed to listen on ' + host + ':' + port + ', code: ' + result), null); + return; + } + + isListening = true; + console.log('[TCP Server] Listening on ' + host + ':' + port); + callback(null, serverSocketId); + }); + }); +} + +// ============================================================================ +// SOCKS5 PROTOCOL HANDLER +// ============================================================================ + +// SOCKS5 constants +var SOCKS_VERSION = 0x05; +var SOCKS_CMD_CONNECT = 0x01; +var SOCKS_ATYP_IPV4 = 0x01; +var SOCKS_ATYP_DOMAIN = 0x03; +var SOCKS_ATYP_IPV6 = 0x04; +var SOCKS_AUTH_NONE = 0x00; +var SOCKS_REPLY_SUCCESS = 0x00; +var SOCKS_REPLY_GENERAL_FAILURE = 0x01; +var SOCKS_REPLY_CONNECTION_NOT_ALLOWED = 0x02; +var SOCKS_REPLY_NETWORK_UNREACHABLE = 0x03; +var SOCKS_REPLY_HOST_UNREACHABLE = 0x04; +var SOCKS_REPLY_CONNECTION_REFUSED = 0x05; + +function initConnection(socketId) { + connections[socketId] = { + state: ConnectionState.GREETING, + buffer: new Uint8Array(0), + destinationSocketId: null + }; + console.log('[SOCKS] Initialized connection ' + socketId); +} + +function removeConnection(socketId) { + var conn = connections[socketId]; + if (conn && conn.destinationSocketId) { + delete reverseConnections[conn.destinationSocketId]; + try { + chrome.sockets.tcp.close(conn.destinationSocketId); + } catch(e) {} + } + delete connections[socketId]; + console.log('[SOCKS] Removed connection ' + socketId); +} + +function handleSocksData(clientSocketId, data) { + var conn = connections[clientSocketId]; + if (!conn) { + console.error('[SOCKS] No connection state for ' + clientSocketId); + return; + } + + // If we're in tunnel mode, forward data to destination + if (conn.state === ConnectionState.TUNNEL && conn.destinationSocketId) { + console.log('[Tunnel] Client -> Destination: ' + data.byteLength + ' bytes'); + chrome.sockets.tcp.send(conn.destinationSocketId, data, function(sendInfo) { + if (sendInfo.resultCode < 0) { + console.error('[Tunnel] Failed to send to destination: ' + sendInfo.resultCode); + closeConnection(clientSocketId); + } + }); + return; + } + + // Append new data to buffer + var newData = new Uint8Array(data); + var newBuffer = new Uint8Array(conn.buffer.length + newData.length); + newBuffer.set(conn.buffer); + newBuffer.set(newData, conn.buffer.length); + conn.buffer = newBuffer; + + // Process based on current state + if (conn.state === ConnectionState.GREETING) { + handleGreeting(clientSocketId, conn); + } else if (conn.state === ConnectionState.REQUEST) { + handleRequest(clientSocketId, conn); + } +} + +function handleGreeting(socketId, conn) { + var data = conn.buffer; + + if (data.length < 3) { + return; // Need more data + } + + var version = data[0]; + var numMethods = data[1]; + + if (version !== SOCKS_VERSION) { + console.error('[SOCKS] Invalid SOCKS version: ' + version); + closeConnection(socketId); + return; + } + + if (data.length < 2 + numMethods) { + return; // Need more data + } + + // Check if no authentication (method 0x00) is supported + var methodSupported = false; + for (var i = 0; i < numMethods; i++) { + if (data[2 + i] === SOCKS_AUTH_NONE) { + methodSupported = true; + break; + } + } + + if (methodSupported) { + sendGreetingResponse(socketId, SOCKS_AUTH_NONE); + conn.state = ConnectionState.REQUEST; + // Clear buffer after greeting + conn.buffer = new Uint8Array(0); + } else { + closeConnection(socketId); + } +} + +function sendGreetingResponse(socketId, method) { + var response = new Uint8Array([SOCKS_VERSION, method]); + chrome.sockets.tcp.send(socketId, response.buffer, function(sendInfo) { + if (sendInfo.resultCode < 0) { + console.error('[SOCKS] Failed to send greeting response: ' + sendInfo.resultCode); + } else { + console.log('[SOCKS] Sent greeting response, method: ' + method); + } + }); +} + +function handleRequest(clientSocketId, conn) { + var data = conn.buffer; + + if (data.length < 4) { + return; // Need more data + } + + var version = data[0]; + var command = data[1]; + var atyp = data[3]; + + if (version !== SOCKS_VERSION) { + console.error('[SOCKS] Invalid SOCKS version in request: ' + version); + sendRequestResponse(clientSocketId, SOCKS_REPLY_GENERAL_FAILURE); + closeConnection(clientSocketId); + return; + } + + if (command !== SOCKS_CMD_CONNECT) { + console.error('[SOCKS] Unsupported command: ' + command); + sendRequestResponse(clientSocketId, SOCKS_REPLY_COMMAND_NOT_SUPPORTED); + closeConnection(clientSocketId); + return; + } + + // Parse address + var host = ''; + var port = 0; + var consumed = 0; + + if (atyp === SOCKS_ATYP_IPV4) { + if (data.length < 10) { + return; // Need more data + } + host = data[4] + '.' + data[5] + '.' + data[6] + '.' + data[7]; + port = (data[8] << 8) | data[9]; + consumed = 10; + } else if (atyp === SOCKS_ATYP_DOMAIN) { + var domainLen = data[4]; + if (data.length < 7 + domainLen) { + return; // Need more data + } + host = ''; + for (var i = 0; i < domainLen; i++) { + host += String.fromCharCode(data[5 + i]); + } + port = (data[5 + domainLen] << 8) | data[6 + domainLen]; + consumed = 7 + domainLen; + } else if (atyp === SOCKS_ATYP_IPV6) { + if (data.length < 22) { + return; // Need more data + } + // Parse IPv6 (simplified) + var parts = []; + for (var j = 0; j < 16; j += 2) { + parts.push(((data[4 + j] << 8) | data[5 + j]).toString(16)); + } + host = parts.join(':'); + port = (data[20] << 8) | data[21]; + consumed = 22; + } else { + console.error('[SOCKS] Unsupported address type: ' + atyp); + sendRequestResponse(clientSocketId, SOCKS_REPLY_ADDRESS_TYPE_NOT_SUPPORTED); + closeConnection(clientSocketId); + return; + } + + console.log('[SOCKS] CONNECT request: ' + host + ':' + port); + + // Create tunnel to destination + createTunnel(clientSocketId, host, port, consumed); +} + +function createTunnel(clientSocketId, host, port, consumed) { + // Clear the request buffer + var conn = connections[clientSocketId]; + conn.buffer = new Uint8Array(0); + + // Create TCP connection to destination + chrome.sockets.tcp.create({}, function(createInfo) { + if (chrome.runtime.lastError || !createInfo) { + console.error('[Tunnel] Failed to create socket:', chrome.runtime.lastError); + sendRequestResponse(clientSocketId, SOCKS_REPLY_GENERAL_FAILURE); + closeConnection(clientSocketId); + return; + } + + var destSocketId = createInfo.socketId; + conn.destinationSocketId = destSocketId; + reverseConnections[destSocketId] = clientSocketId; + + console.log('[Tunnel] Created destination socket: ' + destSocketId); + + // Connect to destination + chrome.sockets.tcp.connect(destSocketId, host, port, function(result) { + if (result < 0) { + console.error('[Tunnel] Failed to connect to ' + host + ':' + port + ', code: ' + result); + + // Map error codes + var reply = SOCKS_REPLY_GENERAL_FAILURE; + if (result === -109) { // Host unreachable + reply = SOCKS_REPLY_HOST_UNREACHABLE; + } else if (result === -111) { // Connection refused + reply = SOCKS_REPLY_CONNECTION_REFUSED; + } else if (result === -102) { // Network unreachable + reply = SOCKS_REPLY_NETWORK_UNREACHABLE; + } + + sendRequestResponse(clientSocketId, reply); + closeConnection(clientSocketId); + return; + } + + console.log('[Tunnel] Connected to ' + host + ':' + port); + + // Send success response to client + sendRequestResponse(clientSocketId, SOCKS_REPLY_SUCCESS); + + // Set to tunnel mode + conn.state = ConnectionState.TUNNEL; + + // Unpause destination socket to start receiving data + chrome.sockets.tcp.setPaused(destSocketId, false); + + console.log('[Tunnel] Tunnel established: client=' + clientSocketId + ' -> dest=' + destSocketId); + }); + }); +} + +function sendRequestResponse(socketId, reply) { + var response = new Uint8Array([ + SOCKS_VERSION, // VER + reply, // REP + 0x00, // RSV + SOCKS_ATYP_IPV4, // ATYP + 0x00, 0x00, 0x00, 0x00, // BND.ADDR + 0x00, 0x00 // BND.PORT + ]); + + chrome.sockets.tcp.send(socketId, response.buffer, function(sendInfo) { + if (sendInfo.resultCode < 0) { + console.error('[SOCKS] Failed to send request response: ' + sendInfo.resultCode); + } else { + console.log('[SOCKS] Sent request response, reply: ' + reply); + } + }); +} + +function closeConnection(socketId) { + chrome.sockets.tcp.close(socketId); + removeConnection(socketId); +} + +// ============================================================================ +// BIDIRECTIONAL TUNNEL DATA FORWARDING +// ============================================================================ + +// Handle data from destination - forward to client +function handleDestinationData(destSocketId, data) { + var clientSocketId = reverseConnections[destSocketId]; + + if (!clientSocketId) { + console.warn('[Tunnel] No client for destination socket: ' + destSocketId); + return; + } + + console.log('[Tunnel] Destination -> Client: ' + data.byteLength + ' bytes'); + + // Pause destination to prevent event spam while sending + chrome.sockets.tcp.setPaused(destSocketId, true); + + chrome.sockets.tcp.send(clientSocketId, data, function(sendInfo) { + // Resume destination after sending + chrome.sockets.tcp.setPaused(destSocketId, false); + + if (sendInfo.resultCode < 0) { + console.error('[Tunnel] Failed to send to client: ' + sendInfo.resultCode); + // Close both connections + closeConnection(clientSocketId); + } + }); +} + +// ============================================================================ +// MAIN INITIALIZATION +// ============================================================================ + +function setupConnectionHandlers() { + // Handle new connections from clients + chrome.sockets.tcpServer.onAccept.addListener(function(acceptInfo) { + console.log('[Proxy] New client connected: ' + acceptInfo.clientSocketId); + + // Initialize SOCKS state for this connection + initConnection(acceptInfo.clientSocketId); + + // Unpause socket to start receiving data + chrome.sockets.tcp.setPaused(acceptInfo.clientSocketId, false); + }); + + // Handle accept errors + chrome.sockets.tcpServer.onAcceptError.addListener(function(errorInfo) { + console.error('[Proxy] Accept error:', errorInfo); + }); + + // Handle incoming data from clients + chrome.sockets.tcp.onReceive.addListener(function(receiveInfo) { + var socketId = receiveInfo.socketId; + + // Check if this is data from a client or destination + if (connections[socketId]) { + // Data from client + console.log('[Proxy] Received from client ' + socketId + ': ' + receiveInfo.data.byteLength + ' bytes'); + handleSocksData(socketId, receiveInfo.data); + } else if (reverseConnections[socketId]) { + // Data from destination + handleDestinationData(socketId, receiveInfo.data); + } else { + console.warn('[Proxy] Received data from unknown socket: ' + socketId); + } + }); + + // Handle receive errors + chrome.sockets.tcp.onReceiveError.addListener(function(errorInfo) { + console.error('[Proxy] Receive error on socket ' + errorInfo.socketId + ':', errorInfo.resultCode); + + // Clean up connections + if (connections[errorInfo.socketId]) { + closeConnection(errorInfo.socketId); + } else if (reverseConnections[errorInfo.socketId]) { + var clientSocketId = reverseConnections[errorInfo.socketId]; + closeConnection(clientSocketId); + } + }); + + // Handle disconnects + chrome.sockets.tcp.onReceive.addListener(function(receiveInfo) { + if (receiveInfo.data.byteLength === 0) { + console.log('[Proxy] Socket ' + receiveInfo.socketId + ' disconnected (EOF)'); + if (connections[receiveInfo.socketId]) { + closeConnection(receiveInfo.socketId); + } else if (reverseConnections[receiveInfo.socketId]) { + closeConnection(reverseConnections[receiveInfo.socketId]); + } + } + }); +} + +function initializeProxy() { + console.log('[Proxy] Initializing Chrome SOCKS Proxy (with tunneling)...'); + + createServer(PROXY_HOST, PROXY_PORT, function(error, socketId) { + if (error) { + console.error('[Proxy] Failed to initialize:', error); + return; + } + + console.log('[Proxy] SOCKS proxy is running on ' + PROXY_HOST + ':' + PROXY_PORT); + setupConnectionHandlers(); + }); +} + +// Chrome App lifecycle events +chrome.app.runtime.onLaunched.addListener(function() { + console.log('[Proxy] App launched'); + initializeProxy(); +}); + +// Also initialize on startup (for apps that are already running) +initializeProxy(); + +console.log('[Proxy] Background script loaded'); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..593c268 --- /dev/null +++ b/manifest.json @@ -0,0 +1,20 @@ +{ + "manifest_version": 2, + "name": "Chrome SOCKS Proxy", + "version": "1.0.0", + "description": "Local SOCKS5 proxy that routes requests through Chrome browser", + "app": { + "background": { + "scripts": ["background.js"] + } + }, + "sockets": { + "tcp": { + "connect": ["*:*"] + }, + "tcpServer": { + "listen": ["*:*"] + } + }, + "permissions": ["storage", ""] +}