This commit is contained in:
Joerg Dorgeist
2026-01-14 10:15:44 +01:00
commit 98feed1934
3 changed files with 482 additions and 0 deletions

448
background.js Normal file
View File

@@ -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');