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

Android VPN接口引发ERR_NETWORK_CHANGED错误的原因排查求助

问题:Android VPN API捕获数据包后无法加载网页,出现ERR_NETWORK_CHANGED错误

我正在Android Studio中开发一款需要监控用户网页浏览行为的Kotlin应用,尝试使用Android VPN API实现该功能,代码编译运行正常,日志显示已成功捕获数据包:

日志片段:

2024-08-07 13:52:02.492 18270-18311 WebFilterVpnService     com.example.pbapp                    D  
Packet captured from 10.0.0.2 to 142.250.68.227
2024-08-07 13:52:02.525 18270-18311 WebFilterVpnService     com.example.pbapp                    D  
Packet captured from 10.0.0.2 to 216.239.32.116
2024-08-07 13:52:02.589 18270-18311 WebFilterVpnService     com.example.pbapp                    D  
Packet captured from 10.0.0.2 to 216.239.32.116
2024-08-07 13:52:02.589 18270-18311 WebFilterVpnService     com.example.pbapp                    D  
Packet captured from 10.0.0.2 to 172.253.63.188
2024-08-07 13:52:02.589 18270-18311 WebFilterVpnService     com.example.pbapp                    D  
Packet captured from 10.0.0.2 to 216.239.32.116
...

不过启动VPN后无法加载任何网站,打开网页约一分钟后抛出ERR_NETWORK_CHANGED错误。

我的VPN初始化函数:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    Log.d(TAG, "VPN Service started")

    val builder = Builder()
        .addAddress("10.0.0.2", 24)
        .addRoute("0.0.0.0", 0)
        .setSession("WebFilterVpnService")

    val configureIntent = PendingIntent.getActivity(
        this,
        0,
        Intent(this, MainActivity::class.java),
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )
    builder.setConfigureIntent(configureIntent)

    vpnInterface = builder.establish()
    if (vpnInterface == null) {
        Log.e(TAG, "Failed to establish VPN interface")
        stopSelf()
        return START_NOT_STICKY
    }
    Log.d(TAG, "VPN interface established")

    startCapture()

    return START_STICKY
}

VPN拦截数据包捕获函数:

private fun startCapture() {
    vpnInterface?.let { vpnInterface ->
        executor.execute {
            Log.d(TAG, "Starting packet capture")
            val fileInputStream = FileInputStream(vpnInterface.fileDescriptor)
            val packet = ByteBuffer.allocate(32767)

            try {
                while (true) {
                    packet.clear()
                    val length = fileInputStream.read(packet.array())
                    if (length > 0) {
                        packet.limit(length)
                        if (length >= 20) {
                            val header = ByteArray(20)
                            packet.position(0)
                            packet.get(header, 0, 20)
                            val sourceIP = "${header[12].toInt() and 0xff}.${header[13].toInt() and 0xff}.${header[14].toInt() and 0xff}.${header[15].toInt() and 0xff}"
                            val destinationIP = "${header[16].toInt() and 0xff}.${header[17].toInt() and 0xff}.${header[18].toInt() and 0xff}.${header[19].toInt() and 0xff}"
                            Log.d(TAG, "Packet captured from $sourceIP to $destinationIP")
                        }
                    }
                }
            } catch (e: Exception) {
                Log.e(TAG, "Error during packet capture", e)
            } finally {
                fileInputStream.close()
            }
        }
    } ?: run {
        Log.e(TAG, "VPN interface is null, cannot start packet capture")
    }
}

我还尝试添加DNS服务器:

builder.addDnsServer("8.8.8.8")
builder.addDnsServer("1.1.1.1")

但没有效果,请问问题出在哪?


解答
  • 核心问题:只捕获数据包但未转发
    Android VPN API的逻辑是:系统把所有流量路由到VPN接口后,你的应用必须负责接收数据包、处理后转发到真实网络,还要把网络返回的响应数据包发回给系统。你的代码目前只做了捕获和日志打印,完全没处理转发,导致所有流量被丢弃,系统收不到响应,最终触发网络错误。

  • 需要补充的关键步骤

    1. 创建输出流处理响应回写:除了读取VPN接口的FileInputStream,还要获取FileOutputStream,用来把网络响应写回给系统。
    2. 建立真实网络连接转发数据包:解析捕获到的数据包的目标IP、端口和协议类型,创建对应的Socket连接(TCP用Socket,UDP用DatagramSocket),把数据包发送到真实网络,同时监听响应并写回VPN输出流。
    3. 多线程管理连接:单个线程无法同时处理多个连接的转发和响应,建议用线程池管理每个连接的处理任务,避免阻塞。
  • 修复示例(简化版)
    修改startCapture函数,添加基础转发逻辑:

private fun startCapture() {
    vpnInterface?.let { vpnInterface ->
        executor.execute {
            Log.d(TAG, "Starting packet capture and forwarding")
            val vpnIn = FileInputStream(vpnInterface.fileDescriptor)
            val vpnOut = FileOutputStream(vpnInterface.fileDescriptor)
            val packet = ByteBuffer.allocate(32767)

            try {
                while (true) {
                    packet.clear()
                    val length = vpnIn.read(packet.array())
                    if (length > 0) {
                        packet.limit(length)
                        if (length >= 20) {
                            val header = ByteArray(20)
                            packet.position(0)
                            packet.get(header, 0, 20)
                            val destIP = "${header[16].toInt() and 0xff}.${header[17].toInt() and 0xff}.${header[18].toInt() and 0xff}.${header[19].toInt() and 0xff}"
                            val protocol = header[8].toInt() and 0xff

                            // 处理UDP数据包(示例)
                            if (protocol == 17) {
                                val udpHeader = ByteArray(8)
                                packet.position(20)
                                packet.get(udpHeader, 0, 8)
                                val destPort = ((udpHeader[2].toInt() and 0xff) shl 8) or (udpHeader[3].toInt() and 0xff)
                                
                                val ds = DatagramSocket()
                                val destAddr = InetAddress.getByName(destIP)
                                val sendPacket = DatagramPacket(packet.array(), length, destAddr, destPort)
                                ds.send(sendPacket)
                                
                                // 接收响应并写回VPN
                                val responseBuffer = ByteArray(32767)
                                val responsePacket = DatagramPacket(responseBuffer, responseBuffer.size)
                                ds.receive(responsePacket)
                                vpnOut.write(responseBuffer, 0, responsePacket.length)
                                ds.close()
                            }
                        }
                    }
                }
            } catch (e: Exception) {
                Log.e(TAG, "Error during packet processing", e)
            } finally {
                vpnIn.close()
                vpnOut.close()
            }
        }
    } ?: run {
        Log.e(TAG, "VPN interface is null, cannot start capture")
    }
}
  • 额外注意事项
    • 权限:确保已申请android.permission.INTERNET和android.permission.BIND_VPN_SERVICE,且Manifest中VPN服务声明时绑定了对应权限。
    • TCP处理:TCP转发逻辑更复杂,需要维护连接状态、处理握手和断开,建议参考成熟的VPN实现框架简化开发。
    • DNS处理:如果要监控DNS请求,需单独处理UDP 53端口的数据包,解析查询内容后再转发或自行解析。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 22:45:01