本地网络权限

任何拥有 INTERNET 权限的应用都可以访问局域网 (LAN) 中的设备。 这让应用可以轻松连接到本地设备,但也会带来隐私问题,例如生成用户指纹,以及推断出位置信息。

本地网络保护项目旨在通过新的运行时权限控制对本地网络的访问,从而保护用户隐私。

影响

从 Android 17(API 级别 37)开始,系统会针对以 Android 17 或更高版本为目标平台的应用强制执行本地网络保护。之前的 Android 16 选择启用配置已弃用;如需了解当前的实现要求,请参阅 Android 17 强制执行。

如果应用使用以下方式访问用户的本地网络,则会受到影响:

  • 直接或通过库在本地网络地址(例如 Multicast DNS (mDNS) 或 Simple Service Discovery Protocol (SSDP))上使用原始套接字。
  • 使用访问本地网络的框架级类,例如 NsdManager。

影响详情

往返于本地网络地址的流量需要本地网络访问权限。下表列出了一些常见情况:

应用底层网络操作 需要本地网络权限
建立传出 TCP 连接 是
接受传入的 TCP 连接 是
发送 UDP 单播、多播、广播 是
接收传入的 UDP 单播、多播、广播 是

这些限制在网络栈深处实现,因此适用于所有网络 API。这包括在平台或受管理代码中创建的套接字、Cronet 和 OkHttp 等网络库,以及在这些库之上实现的任何 API。尝试解析本地网络上具有 .local 后缀的服务需要本地网络权限。

上述规则的例外情况:

  • 如果设备的 DNS 服务器位于本地网络中,则进出该服务器(端口 53)的流量不需要本地网络访问权限。
  • 使用输出切换器作为应用内选择器的应用不需要本地网络权限(后续版本中将提供更多指导)。

Android 17 违规处置

从 Android 17 开始,本地网络保护是强制性的,并且会针对以 Android 17 或更高版本为目标平台的应用强制执行。

切面 Android 16 Android 17
目标 SDK 版本 36 37 或更高版本
权限 临时使用了 NEARBY_WIFI_DEVICES ACCESS_LOCAL_NETWORK
默认访问权限 本地网络访问权限已开启 对于所有更新了目标 SDK 的应用,默认情况下本地网络会被屏蔽
权限组 属于现有 NEARBY_DEVICES 权限组

为验证强制执行后应用功能是否正常,以 SDK 37 或更高版本为目标平台的应用必须采用以下路径之一来管理本地网络访问权限:

路径 A:使用可保护隐私的选择器

对于系统介导的发现和连接任务,请使用选择器,以完全避免请求广泛的运行时权限。 根据您的应用场景,使用以下选择器:

  • 媒体流式传输:支持 Google Cast 的应用可以使用输出切换器功能。这样,开发者就可以让用户选择特定的流媒体设备,而无需应用请求广泛的 ACCESS_LOCAL_NETWORK 权限。
  • 常规连接性:NsdManager 包含一个系统运行的服务选择器,用于 mDNS 发现。系统会显示一个对话框,让用户选择一个设备供应用访问,而不是让应用扫描整个网络。
val discoveryRequest = DiscoveryRequest.Builder("_http._tcp")
    .setFlags(DiscoveryRequest.FLAG_SHOW_PICKER)
    .build()

nsdManager.registerServiceInfoCallback(discoveryRequest, executor, object : NsdManager.ServiceInfoCallback {
    override fun onServiceUpdated(serviceInfo: NsdServiceInfo) {
        // Handle the user-selected and discovered service
        // NsdServiceInfo.getHostAddresses() can now be connected to
        // without ACCESS_LOCAL_NETWORK permission
    }
})

路径 B:请求运行时权限(广泛访问权限)

对于需要广泛、持续访问本地网络的复杂用例(例如家居自动化或物联网设备管理),此路径是必需的。

  • 在清单中声明权限:在 AndroidManifest.xml 中明确声明 ACCESS_LOCAL_NETWORK。

  • 在运行时请求权限:在尝试任何本地网络访问之前,应用必须检查是否已获得相应权限。否则,它们必须调用 Activity.requestPermission() 来触发标准系统提示。

  • 预先授予权限的情形:ACCESS_LOCAL_NETWORK 权限是 NEARBY_DEVICES 权限组的一部分。如果用户已授予此群组中的其他权限(例如蓝牙权限),系统不会再次提示他们授予本地网络访问权限。

  • 处理拒绝和撤销:应用必须妥善处理用户拒绝请求或稍后在系统设置中撤销权限的情况。在这种情况下,本地网络流量将被屏蔽。

权限请求重置计数器策略

该平台实施了一种计数器重置策略,以应对以下情况:应用之前拒绝了 NEARBY_DEVICES 权限组(现在包含 ACCESS_LOCAL_NETWORK),导致应用在充分说明其理由后无法请求该权限。此机制会为应用提供更多调用 requestPermission() API 的机会,从而有效重置 ACCESS_LOCAL_NETWORK 权限的拒绝次数。这样一来,您就可以更细致地重新吸引用户,尤其是在应用无法传达其核心功能需要本地网络访问权限之前,用户就已拒绝授予权限的情况下。

拆分权限模型

本地网络权限采用拆分权限迁移策略,根据目标 SDK 版本对新应用和旧应用进行不同的处理

类别 目标 SDK 级别 本地网络访问行为 开发者需要采取的行动
新应用/更新后的应用 >= 37 (Android 17) 默认屏蔽 声明和请求 ACCESS_LOCAL_NETWORK 运行时权限
旧版应用 < 37 具有 INTERNET 权限的应用会收到 ACCESS_LOCAL_NETWORK 的隐式权限授予,从而使其能够保留访问权限。这是临时性的,一旦应用将目标 SDK 升级到 37,系统就会默认屏蔽此操作 无需立即更改代码。在以 SDK 37 为目标平台之前,请勿在运行时请求 ACCESS_LOCAL_NETWORK。

LNP Strategy by Use Case

  • 投射:对于媒体投射功能,最合适且可保护隐私的策略是使用输出切换器。此方法允许系统代表用户处理本地网络发现和连接,从而无需应用请求 ACCESS_LOCAL_NETWORK 权限。

  • 浏览器:处理错误需要根据协议采用不同的方法。UDP 错误会导致 EPERM 错误代码。对于 TCP 连接,浏览器应使用 NDK API android_getnetworkblockedreason(int sockFd) 来确定数据包是否被 LNP 屏蔽,此 API 会返回 ANDROID_NETWORK_BLOCKED_REASON_LNP。

  • 其他用例(例如 IoT):使用 mDNS 查找设备的应用应使用 android.net.nsd.DiscoveryRequest#FLAG_SHOW_PICKER(允许在没有权限的情况下查找设备)和 NsdManager#registerServiceInfoCallback / NsdManager#resolveService(用于获取 IP 地址)。连接到通过这种方式获取的 IP 地址不需要 ACCESS_LOCAL_NETWORK 权限。

对于需要直接进行本地网络通信且无法使用系统介导的选择器的应用,如果以 Android 17 (SDK 37) 或更高版本为目标平台,建议采用权限重置计数器策略。如果用户撤消了 ACCESS_LOCAL_NETWORK 权限,此机制会为应用提供更多机会来重新请求该权限,从而让开发者能够向用户提供更清晰的理由。

Android 16 指南(已废弃)

如需在 Android 16 上选择启用本地网络限制,请执行以下操作:

  1. 将设备刷写到搭载 Android 16 Beta 3 或更高版本的 build
  2. 安装要测试的应用
  3. 使用 adb 切换 Appcompat 配置

    adb shell am compat enable RESTRICT_LOCAL_NETWORK <package_name>
    
  4. 重启设备

现在,您的应用对本地网络的访问权限受到限制,任何访问本地网络的尝试都会导致套接字错误。 如果您使用的 API 在应用进程之外执行本地网络操作(例如 NsdManager),则这些 API 在选择启用期间不会受到影响。

如需恢复访问权限,您必须向应用授予NEARBY_WIFI_DEVICES权限。

  • 确保应用在其 manifest 中声明了 NEARBY_WIFI_DEVICES 权限。
  • 依次前往设置 > 应用 > [应用名称] > 权限 > 附近的设备 > 允许

现在,您的应用对本地网络的访问权限应该已恢复,并且所有场景都应该像选择启用该应用之前一样正常运行。以下是应用网络流量受到的影响。

权限 出站 LAN 请求 出站/入站互联网请求 入站 LAN 请求
已授予 Works Works Works
未授予 最差排行榜 Works 最差排行榜

使用以下命令关闭 Appcompat 配置

adb shell am compat disable RESTRICT_LOCAL_NETWORK <package_name>

错误

如果本地网络访问请求因缺少权限而失败:

  • TCP 连接通常会导致超时错误。

  • UDP 错误和一般权限拒绝通常会导致 EPERM 错误代码

bug

提交以下方面的错误和反馈:

  • LAN 访问权限存在差异(您认为不应将某项访问权限视为“本地网络”访问权限)
  • 本应禁止但未禁止 LAN 访问的 bug
  • 不应阻止 LAN 访问但却阻止了的 bug

以下内容应该不会受到此变更的影响:

  • 连接到互联网
  • 移动网络