Works
This commit is contained in:
14
app.html
Normal file
14
app.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Chrome App</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Chrome App Test</h1>
|
||||
<p>If you see this, the Chrome App loaded successfully!</p>
|
||||
<p>Check the console for TCP server status.</p>
|
||||
<script>
|
||||
console.log('App window loaded');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
448
background.js
Normal file
448
background.js
Normal 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');
|
||||
20
manifest.json
Normal file
20
manifest.json
Normal file
@@ -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", "<all_urls>"]
|
||||
}
|
||||
Reference in New Issue
Block a user