You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android VpnService(JSch SSH连接)能连服务器但无法访问互联网

问题原因与解决方案

你的代码虽成功建立SSH连接与VPN虚拟接口,但未实现VPN接口与SSH代理间的流量转发逻辑,这是无法访问互联网的核心原因。此外代码存在几处关键配置错误,具体问题与修复步骤如下:


1. 代码中的核心错误

(1)重复调用establish()导致配置无效

构建VPN时连续调用两次builder.establish(),且allowFamily()在首次establish()后添加,这部分配置完全不生效:

// 错误:首次调用establish()后,builder配置已固化,后续修改无效
builder.setSession("MyVpnService")
        .addAddress("10.0.0.1", 24)
        .addRoute("0.0.0.0", 0)
        .addDnsServer("8.8.8.8")
        .setBlocking(true)
        .establish(); // 首次调用,配置已生效

vpnInterface = builder.establish(); // 重复调用无意义
builder.allowFamily(OsConstants.AF_INET); // 此代码在establish后执行,完全无效

(2)缺失VPN接口流量的读写转发逻辑

Android的VpnService仅创建虚拟网卡、将系统流量导向该接口,但你必须自行编写代码读取vpnInterface内的流量,通过SSH的SOCKS5代理转发至VPS,再将VPS返回的流量写回vpnInterface。你的代码完全缺少这部分核心逻辑,导致流量到VPN接口后直接中断。

(3)SSH端口转发配置需验证

你设置的端口转发sshSession.setPortForwardingL(0, "localhost", 1080),表示将本地随机端口转发到VPS的localhost:1080,需确保VPS的1080端口确实运行着SOCKS5服务(比如通过sshd开启或专用SOCKS5代理)。


2. 修复后的完整代码示例

(1)修改MyVpnService类

核心是添加流量转发线程,处理VPN接口的读写,并通过SOCKS5代理转发流量:

package com.example.myapplication;

import android.content.Intent;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;
import android.system.OsConstants;
import android.util.Log;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Socket;

public class MyVpnService extends VpnService {
    private ParcelFileDescriptor vpnInterface;
    private Session sshSession;
    private Thread trafficForwardThread;
    private int localSocksPort;

    @Override
    public void onCreate() {
        super.onCreate();
        JSch jsch = new JSch();
        try {
            // 替换为你的VPS信息
            sshSession = jsch.getSession("SERVER_USERNAME", "SERVER_IP", SERVER_PORT);
            sshSession.setPassword("SERVER_PASSWORD");
            sshSession.setConfig("StrictHostKeyChecking", "no");
        } catch (JSchException e) {
            Log.e("MyVpnService", "SSH初始化失败", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Thread vpnThread = new Thread(() -> {
            try {
                // 建立SSH连接
                sshSession.connect();
                // 将本地随机端口转发到VPS的SOCKS5端口(假设VPS的SOCKS5运行在1080)
                localSocksPort = sshSession.setPortForwardingL(0, "localhost", 1080);
                Log.d("MyVpnService", "本地SOCKS5端口:" + localSocksPort);

                // 正确配置VPN接口,仅调用一次establish()
                VpnService.Builder builder = new VpnService.Builder();
                vpnInterface = builder.setSession("MyVpnService")
                        .addAddress("10.0.0.2", 24) // 客户端地址,避免与VPS内网冲突
                        .addRoute("0.0.0.0", 0) // 路由所有IPv4流量
                        .addDnsServer("8.8.8.8")
                        .allowFamily(OsConstants.AF_INET)
                        .setBlocking(true)
                        .establish();

                // 启动流量转发线程
                startTrafficForwarding();

            } catch (Exception e) {
                Log.e("MyVpnService", "VPN启动失败", e);
                cleanup();
            }
        });
        vpnThread.start();
        return START_STICKY;
    }

    // 核心:处理VPN接口流量转发到SOCKS5代理
    private void startTrafficForwarding() {
        trafficForwardThread = new Thread(() -> {
            FileInputStream in = new FileInputStream(vpnInterface.getFileDescriptor());
            FileOutputStream out = new FileOutputStream(vpnInterface.getFileDescriptor());

            try {
                byte[] buffer = new byte[1500];
                while (!Thread.interrupted()) {
                    // 读取VPN接口的流量
                    int length = in.read(buffer);
                    if (length > 0) {
                        // 连接到本地SOCKS5代理端口
                        try (Socket socksSocket = new Socket("localhost", localSocksPort)) {
                            // 将流量发送到SOCKS5代理
                            socksSocket.getOutputStream().write(buffer, 0, length);
                            // 读取代理返回的流量,写回VPN接口
                            int responseLength = socksSocket.getInputStream().read(buffer);
                            if (responseLength > 0) {
                                out.write(buffer, 0, responseLength);
                                out.flush();
                            }
                        } catch (IOException e) {
                            Log.e("MyVpnService", "流量转发失败", e);
                        }
                    }
                }
            } catch (IOException e) {
                Log.e("MyVpnService", "流量读写失败", e);
            } finally {
                try {
                    in.close();
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        trafficForwardThread.start();
    }

    private void cleanup() {
        if (trafficForwardThread != null) {
            trafficForwardThread.interrupt();
        }
        if (vpnInterface != null) {
            try {
                vpnInterface.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (sshSession != null && sshSession.isConnected()) {
            sshSession.disconnect();
        }
    }

    @Override
    public void onDestroy() {
        cleanup();
        super.onDestroy();
    }
}

(2)额外注意事项

  • 确保VPS已开启SOCKS5代理:可在/etc/ssh/sshd_config中添加GatewayPorts yes与AllowTcpForwarding yes,重启sshd服务后,通过ssh -D 1080 user@vps-ip验证SOCKS5可用性。
  • Android 12及以上版本需POST_NOTIFICATIONS权限,需在Manifest中添加并动态申请,否则前台服务无法正常运行。
  • 上述流量转发逻辑为基础实现,可进一步优化(如使用NIO或连接池)。

内容的提问来源于stack exchange,提问作者soheyl momeni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 15:07:03