shadowsocksrr / shadowsocksr-android

A ShadowsocksR client for Android
7.74k stars 1.33k forks source link

开启SSRR后,Android 8.1/P不认Wifi #89

Open kcschan opened 6 years ago

kcschan commented 6 years ago

这个故障用8.1的应该都比较熟悉了。

简单描述一下复现方法: 系统版本:Android 8.1~P(DP1) 设备接在WiFi上,开启SSRR(本身服务器连接正常)

故障描述: 这个时候,很多应用会认为处于移动网络/metered network而拒绝使用它们认为应该仅在WiFi下进行的操作,如: Google photos拒绝备份照片,除非允许cellular backup 下载更新时,会一直停留在install update,但实际并不会进行下载的状态

到了Android P变得更麻烦,play store无法安装应用(因为它认为处于metered network),可以通过开启play store的unrestricted data usage来临时处理

按照今天的声明,看上去与8.1~P的VPN接口改动有关。

官方到今天才正式声明了这件事,但实际上8.1就应该已经包含了至少一部分的改动:

https://issuetracker.google.com/u/2/issues/68657525

https://developer.android.com/preview/behavior-changes.html 关注#More detailed network capabilities reporting for VPNs 这一节

看上去需要SSRR根据实际网络情况动态调用setUnderlyingNetworks()

https://developer.android.com/reference/android/net/VpnService.html#setUnderlyingNetworks(android.net.Network[])

在madeye的shadowsocks方面也有人指出这件事: https://github.com/shadowsocks/shadowsocks-android/issues/1666

Akkariiin commented 6 years ago

感谢反馈

Akkariiin commented 6 years ago

emmmmm , 根据这里的说法:

https://developer.android.com/reference/android/net/VpnService.html#setUnderlyingNetworks(android.net.Network[])

setUnderlyingNetworks

added in API level 22

boolean setUnderlyingNetworks (Network[] networks)

Sets the underlying networks used by the VPN for its upstream connections.

Used by the system to know the actual networks that carry traffic for apps affected by this VPN in order to present this information to the user (e.g., via status bar icons).

This method only needs to be called if the VPN has explicitly bound its underlying communications channels — such as the socket(s) passed to protect(int) — to a Network using APIs such as bindSocket(Socket) or bindSocket(DatagramSocket). The VPN should call this method every time the set of Networks it is using changes.

networks is one of the following:

*    a non-empty array: an array of one or more Networks, in decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular) networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear first in the array.
*    an empty array: a zero-element array, meaning that the VPN has no underlying network connection, and thus, app traffic will not be sent or received.
*    null: (default) signifies that the VPN uses whatever is the system's default network. I.e., it doesn't use the bindSocket or bindDatagramSocket APIs mentioned above to send traffic over specific channels.

This call will succeed only if the VPN is currently established. For setting this value when the VPN has not yet been established, see setUnderlyingNetworks(Network[]).

Parameters
--
networks | Network: An array of networks the VPN uses to tunnel traffic to/from its servers.

Returns
---
boolean | true on success. 

这个地方说仅仅只能在已建立VPN连接时使用,而且在变更时需要重新告知系统新的底层网络是什么。 (这里的变更应该是指变更使用的底层网络,也就是说存在某些可能实际上不会连接网络的VPN,也可能存在支持负载均衡同时使用移动数据和wifi的应用。) 所以我觉得SS/SSR这种情况直接调用 setUnderlyingNetworks (null) 来告诉系统使用的是默认系统连接就好。

但是又说,上面这个只能在以建立连接时使用,还未建立时应该使用以下这个版本:

https://developer.android.com/reference/android/net/VpnService.Builder.html#setUnderlyingNetworks(android.net.Network[])

setUnderlyingNetworks

added in API level 22

VpnService.Builder setUnderlyingNetworks (Network[] networks)

Sets the underlying networks used by the VPN for its upstream connections.

Parameters
networks | Network: An array of networks the VPN uses to tunnel traffic to/from its servers.

Returns
VpnService.Builder | this VpnService.Builder object to facilitate chaining method calls. 

See also:
    setUnderlyingNetworks(Network[])

这个版本起调于VpnService.Builder,也就是配置vpn时使用,但却没有详细说明使用方法,而是引用了上面那个版本。 所以我猜应该在启动vpn之前在VpnService.Builder上直接 setUnderlyingNetworks (null) 设置一下就好了。剩下的就不用管了。撒花~~~

kcschan commented 6 years ago

可以用官方模拟器配套的带“play”字样的镜像(不是"google apis",前者才有play store)测试一下。

这个故障在P变得更麻烦的表现是,似乎是扯上download manager的包括第三方应用都会遇到。

kcschan commented 6 years ago

VpnService.setUnderlyingNetworksForVpn最后应该是由这里执行:

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/services/core/java/com/android/server/ConnectivityService.java setUnderlyingNetworksForVpn(Network[] networks)

public boolean setUnderlyingNetworksForVpn(Network[] networks) {
    int user = UserHandle.getUserId(Binder.getCallingUid());
    final boolean success;
    synchronized (mVpns) {
        throwIfLockdownEnabled();
        success = mVpns.get(user).setUnderlyingNetworks(networks);
    }
    if (success) {
        mHandler.post(() -> notifyIfacesChangedForNetworkStats());
    }
    return success;
}

接下来看Vpn.java:

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/services/core/java/com/android/server/connectivity/Vpn.java

public Builder setUnderlyingNetworks(Network[] networks) {
--
  | mConfig.underlyingNetworks = networks != null ? networks.clone() : null;
  | return this;
  | }

303行的 updateCapabilities(ConnectivityManager cm, Network[] underlyingNetworks, NetworkCapabilities caps)

里面有这么一句

        if (ArrayUtils.isEmpty(underlyingNetworks)) {
            // No idea what the underlying networks are; assume sane defaults
            metered = true;   //
            roaming = false;
            congested = false;
        } else {
            for (Network underlying : underlyingNetworks) {
                final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying);
                if (underlyingCaps == null) continue;
                for (int underlyingType : underlyingCaps.getTransportTypes()) {
                    transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType);
                }

                // When we have multiple networks, we have to assume the
                // worst-case link speed and restrictions.
                downKbps = NetworkCapabilities.minBandwidth(downKbps,
                        underlyingCaps.getLinkDownstreamBandwidthKbps());
                upKbps = NetworkCapabilities.minBandwidth(upKbps,
                        underlyingCaps.getLinkUpstreamBandwidthKbps());
                metered |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_METERED);
                roaming |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
                congested |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_CONGESTED);
            }
        }

        caps.setTransportTypes(transportTypes);
        caps.setLinkDownstreamBandwidthKbps(downKbps);
        caps.setLinkUpstreamBandwidthKbps(upKbps);
        caps.setCapability(NET_CAPABILITY_NOT_METERED, !metered);
        caps.setCapability(NET_CAPABILITY_NOT_ROAMING, !roaming);
        caps.setCapability(NET_CAPABILITY_NOT_CONGESTED, !congested);

这么一来underlyingNetworks一旦是null和空数组,metered 一定会变成 true,然后便没有NET_CAPABILITY_NOT_METERED --> BOOM

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/services/core/java/com/android/server/ConnectivityService.java 的 public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId)

也有一段类似意思的注释,大致上说VPN Service下面有什么网络,就把它们全部算上

iKirby commented 6 years ago

昨天试过在 VpnService.Builder 那里调用 setUnderlyingNetworks(null) 无效(原版 SS)。 另外目前官方的模拟器在运行 P 的时候无法打开 Wi-Fi ,所以没办法用模拟器测试。

Akkariiin commented 6 years ago

@kcschan 这样呀 那么这么说就是需要传当前VPN应用下层的网络类型了么? 那么如何获取当前的真实的下层网络类型而不是VPN自己广播出去的类型呢?

PS: 下载管理器的行为应该和play store是一样的,因为play store其实也是调用下载管理器来下载的 PS2: 现在暂时没法开安卓虚拟机,因为这个项目现在没法在win上build,但是我又暂时没有安全的linux的虚拟机宿主机

Akkariiin commented 6 years ago

@iKirby @kcschan 我在想,现在的解决方法要么就是把wifi和蜂窝两个一起放进去,但是这样的话按照那个代码就会限速在蜂窝网络下;要么就默认传wifi;要么就给个配置按钮让使用者自己选择。

希望有官方的标准解决方法范例,或者我上面说的可以获取当前的真实的下层网络类型,那么监听一下就OK了。但是要怎么做呢?安卓我还是新手欸~~

iKirby commented 6 years ago

@Akkariiin 刚才试了获取当前活动的网络(确定是 Wi-Fi ,用 [toString()](https://developer.android.com/reference/android/net/Network.html#toString()) 方法看的)并传进去但是也无效,不知道是不是方法不对。 对于 Android 我也是新手。官方文档说的又不清楚,搜索过也没找到其他例子,不知道到底应该在什么时候调用这个方法。

Akkariiin commented 6 years ago

@iKirby 摊手 那么如果一直传入wifi,其他应用(比如play)会不会在流量下也识别为wifi呢 如果是的话,在官方修正或提供标准做法之前,就按照上面我说的第三个方法(给个配置按钮让使用者自己选择)来做好了。

iKirby commented 6 years ago

@Akkariiin 关键是尝试了传入 Wi-Fi 没有用啊... 如果能选择那也不错。

kcschan commented 6 years ago

@Akkariiin

ss那边看上去已经找到问题了,看样子@iKirby 提到的办法在P应当有效,但在8.1无效

https://github.com/shadowsocks/shadowsocks-android/commit/3a8f863bd1f0deed081de082a674020368f30319

他们提到一个workaround:

An interesting observation is that the developer can call setUnderlyingNetworks(arrayOfNulls(1)) to make the VPN connection always unmetered

@iKirby 试试看这个办法?

iKirby commented 6 years ago

@kcschan 我是用 P 测试的,我的修改确实无效。现在正在重新编译 beta 分支的 SS 测试。

kcschan commented 6 years ago

@iKirby 在P里面用这个呢? setUnderlyingNetworks(arrayOfNulls(1))

iKirby commented 6 years ago

@kcschan 刚才试了一下,没用。我编译 beta 分支的 apk 无法安装... master 分支没问题,但是加上了这个也没有用

Akkariiin commented 6 years ago

额........... setUnderlyingNetworks(arrayOfNulls(1)) 其实就是上面所说的 * an empty array: a zero-element array, meaning that the VPN has no underlying network connection, and thus, app traffic will not be sent or received.

iKirby commented 6 years ago

@kcschan @Akkariiin 测试过了 SS master 分支 + https://github.com/shadowsocks/shadowsocks-android/commit/3a8f863bd1f0deed081de082a674020368f30319 的代码(因为 beta 分支的我编译出来装不上)。在 Android P 上,Google Photos 和 Drive 可以正确检测到 WiFi 网络并且自动开始上传任务,但是 Play Store 仍然无法下载应用,Telegram X 还是会识别为移动网络。

ghost commented 6 years ago

@iKirby 那个编译后需要使用 adb install -t xx-debug.apk 才能安装, 我刚才也重新编译了一下, 大体的状态和你的一样, 很纠结 (

iKirby commented 6 years ago

@Mrlk 奇怪... Manifest 里面没有看到 android:testOnly="true" 啊,但是生成 release 的 apk 也会被加上这个...

EDIT: 搜了一下,是因为 alpha 版本的 com.android.tools.build:gradle 会给生成的 apk 自动加上这个

ghost commented 6 years ago

@iKirby 现在有什么可用的解决方案吗 现在手机play商店什么都下不了 😐

iKirby commented 6 years ago

@Mrlk 同一个局域网的其他设备开一个 http 代理,连接 WiFi 时候代理设置为那个就可以了。我现在是用电脑开一个热点设置代理,需要下载就切换到这个热点,这样不用每次都修改 WiFi 设置

ghost commented 6 years ago

@iKirby 好的 我尝试一下

Cirooo commented 6 years ago

我还以为是google做了什么限制或者用了别的什么协议导致不能用ssr OTA升级。。。 Pixel Android 8.1

kcschan commented 6 years ago

@iKirby 先把 https://github.com/shadowsocks/shadowsocks-android/issues/1666 的fix port过来?

kcschan commented 6 years ago

@iKirby cherrypick这个commit?

https://github.com/shadowsocks/shadowsocks-android/pull/1698 https://github.com/shadowsocks/shadowsocks-android/commit/f7083025a0136f4eb4f5fc7e74d0d46f15cdcde9

iKirby commented 6 years ago

@kcschan 我不是这边的维护者,只是看到这边也有讨论,就来说一下自己的意见。我一直用的原版

Akkariiin commented 6 years ago

@kcschan 可以照着实现一遍 刚才看了一下基本看懂了

Akkariiin commented 6 years ago

我不太懂为什么这里可以不protect这个fd https://github.com/shadowsocks/shadowsocks-android/pull/1698/files#diff-5de104e13c252a2e4c1876ebe9e409d1R84

@kcschan @iKirby

kcschan commented 6 years ago

@iKirby 抱歉at错人 @Akkariiin 不懂那个fd

Akkariiin commented 6 years ago

mark https://github.com/shadowsocks/shadowsocks-android/commit/3a8f863bd1f0deed081de082a674020368f30319

kcschan commented 6 years ago

四月补丁的这个commit修复了一部分上传的bug,供参考 @Akkariiin

https://github.com/AospExtended/platform_frameworks_base/commit/db0f5100c9bdf8d0d26554f8e4aa3d4238757cb3#diff-3992263f882eea44ac9d5e611e713966

Cirooo commented 6 years ago

我的可以google play更新。只不过不能ota升级。Pixel

leoxxx commented 5 years ago

7.1.1也一样。 https://github.com/shadowsocksrr/shadowsocksr-android/issues/240 这是我的解决方案,没ROOT就没办法了,其它软件的VPN模式也是这个样子。

tyskink commented 4 years ago

Google play可以通过清理缓存和app data的方式。昨天我的Google photo偷偷用了我两个G的流量,感觉是它把流量档wifi用了,会跟SSRR有关系吗?