Android 24+全局代理问题:VPN SocketChannel代理配置失败求助
Fixing VPN Traffic Forwarding to External Proxy on Android 24+
Hey there, let's break down why your current code isn't working and how to fix it to route VPN traffic through your external proxy properly.
Why Your Existing Code Fails
- First approach: You're connecting directly to the destination, so no proxy is involved at all—this bypasses your external proxy entirely.
- Second approach: You connect to the proxy, but then try to call
connect()again to the destination. That's not how proxies work: once connected to the proxy, you need to send a proxy-specific handshake request (like HTTP CONNECT or SOCKS5) to ask the proxy to establish a tunnel to your target, not callconnect()directly.
Correct Implementation Steps
The key is to:
- Establish a connection to your external proxy
- Complete the proxy handshake to create a tunnel to the destination
- Use the established tunnel to send/receive traffic from the destination
Below are examples tailored to fit with the TCPOutput structure from LocalVPN:
Example 1: HTTP/HTTPS Proxy (Using CONNECT Method)
// Step 1: Connect to proxy and protect the socket from VPN loopback Socket proxySocket = new Socket("PROXY.COM", 8080); vpnService.protect(proxySocket); // Critical: Prevents this socket's traffic from being routed back through VPN SocketChannel outputChannel = proxySocket.getChannel(); outputChannel.configureBlocking(false); // Step 2: Send HTTP CONNECT request to proxy to create tunnel to destination String connectRequest = String.format( "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\nConnection: keep-alive\r\n\r\n", destinationAddress, destinationPort, destinationAddress, destinationPort ); ByteBuffer requestBuffer = ByteBuffer.wrap(connectRequest.getBytes(StandardCharsets.UTF_8)); // Handle non-blocking write (match LocalVPN's async IO pattern) while (requestBuffer.hasRemaining()) { int bytesWritten = outputChannel.write(requestBuffer); if (bytesWritten == 0) { // Wait for writable event via Selector (as done in TCPOutput) break; } } // Step 3: Verify proxy's response (wait for readable event first in non-blocking mode) ByteBuffer responseBuffer = ByteBuffer.allocate(1024); int bytesRead = outputChannel.read(responseBuffer); if (bytesRead > 0) { responseBuffer.flip(); String response = new String(responseBuffer.array(), 0, bytesRead, StandardCharsets.UTF_8); if (!response.startsWith("HTTP/1.1 200")) { // Proxy rejected the CONNECT request—handle error and close socket proxySocket.close(); return; } } // Step 4: Now you can use outputChannel to send/receive traffic to the destination // This replaces the direct destination connection logic in TCPOutput
Example 2: SOCKS5 Proxy
If you're using a SOCKS5 proxy, the handshake sequence is different:
// Step 1: Connect to SOCKS5 proxy and protect the socket Socket proxySocket = new Socket("PROXY.COM", 1080); vpnService.protect(proxySocket); SocketChannel outputChannel = proxySocket.getChannel(); outputChannel.configureBlocking(false); // Step 2: SOCKS5 initial handshake (no authentication) ByteBuffer handshakeBuffer = ByteBuffer.allocate(3); handshakeBuffer.put((byte) 0x05); // SOCKS version 5 handshakeBuffer.put((byte) 0x01); // Number of authentication methods handshakeBuffer.put((byte) 0x00); // No authentication required handshakeBuffer.flip(); outputChannel.write(handshakeBuffer); // Read handshake response ByteBuffer authResponse = ByteBuffer.allocate(2); outputChannel.read(authResponse); authResponse.flip(); if (authResponse.get() != 0x05 || authResponse.get() != 0x00) { proxySocket.close(); return; } // Step 3: Send SOCKS5 connect request to destination ByteBuffer connectRequest = ByteBuffer.allocate(10 + destinationAddress.length()); connectRequest.put((byte) 0x05); // Version connectRequest.put((byte) 0x01); // CONNECT command connectRequest.put((byte) 0x00); // Reserved connectRequest.put((byte) 0x03); // Domain name type connectRequest.put((byte) destinationAddress.length()); // Domain length connectRequest.put(destinationAddress.getBytes(StandardCharsets.UTF_8)); // Domain connectRequest.putShort((short) destinationPort); // Port connectRequest.flip(); outputChannel.write(connectRequest); // Read connect response ByteBuffer connectResponse = ByteBuffer.allocate(10); outputChannel.read(connectResponse); connectResponse.flip(); if (connectResponse.get() != 0x05 || connectResponse.get() != 0x00) { proxySocket.close(); return; } // Now ready to transfer traffic through the tunnel
Key Notes for Integration with LocalVPN
- Non-blocking IO: Since LocalVPN uses
Selectorfor async IO, you'll need to register the proxySocketChannelwith the selector and handle write/read events asynchronously (just like the original TCPOutput does for direct connections). - Avoid VPN Loopback: Always call
vpnService.protect()on the proxy socket—this tells Android not to route the proxy-bound traffic back through your VPN, which would cause an infinite loop. - Error Handling: Add proper error handling for partial writes/reads, proxy timeouts, and failed handshakes (matching the existing error handling in TCPOutput).
内容的提问来源于stack exchange,提问作者DrBrad




