Android 15网络子系统深度解析(二):网络管理策略框架解析

255 阅读18分钟

引言

在上一篇文章中,我们深入探索了ConnectivityService的网络管理框架。今天,我们将聚焦于网络子系统的另外两个核心功能:网络策略管理VPN框架

为什么需要网络策略管理?

想象这样的场景:

场景1:流量失控

用户月底收到运营商短信:"您的流量已用完95%"
查看系统设置发现:
- 某个后台应用偷偷下载了2GB更新包
- 多个应用在后台同步数据
- 系统服务持续上传统计数据

场景2:电量告急

用户外出发现:
- 手机只剩15%电量
- 但至少还要2小时才能充电
- 很多应用还在后台联网消耗电量

这时候,Android的**NetworkPolicy(网络策略)**系统就派上用场了!

本文内容概览

  1. NetworkPolicy流量控制

    • NetworkPolicyManagerService架构
    • Data Saver省流模式
    • 按流量计费网络识别
    • 流量统计与限制
  2. VPN框架深度解析

    • VpnService工作原理
    • TUN/TAP虚拟网卡
    • VPN路由配置
    • Always-on VPN
  3. 网络性能优化

    • 流量优化技巧
    • DNS优化
    • 连接池管理
    • 网络诊断工具
  4. Android 15新特性

    • eBPF网络策略
    • VPN改进
    • 网络切片支持

让我们开始这段深入Android网络核心的旅程!


一、NetworkPolicy流量控制深度解析

1.1 NetworkPolicyManagerService架构总览

源码位置

packages/modules/Connectivity/service/src/com/android/server/net/NetworkPolicyManagerService.java

架构图

08-01-network-policy-architecture.png

核心职责

┌────────────────────────────────────────┐
│   NetworkPolicyManagerService 核心功能  │
├────────────────────────────────────────┤
│ 1. 流量监控                             │
│    - 实时统计应用流量                   │
│    - 按UID/Interface统计                │
│    - 历史流量记录                       │
│                                        │
│ 2. 策略执行                             │
│    - Data Saver模式                    │
│    - App Standby限制                   │
│    - 按流量计费网络控制                 │
│                                        │
│ 3. 流量限制                             │
│    - 后台数据禁用                       │
│    - 流量配额管理                       │
│    - 防火墙规则                         │
│                                        │
│ 4. 网络识别                             │
│    - Metered/Unmetered区分             │
│    - WiFi/移动网络判断                  │
│    - 运营商计费策略                     │
└────────────────────────────────────────┘

核心数据结构

// packages/modules/Connectivity/.../NetworkPolicyManagerService.java
public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {

    // 网络策略集合 (按NetworkTemplate分类)
    private final ArrayMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy =
        new ArrayMap<>();

    // UID策略 (每个应用的网络限制策略)
    private final SparseIntArray mUidPolicy = new SparseIntArray();

    // UID规则 (防火墙规则:允许/拒绝)
    private final SparseIntArray mUidRules = new SparseIntArray();

    // 按流量计费网络集合
    private final ArraySet<NetworkTemplate> mMeteredIfaces = new ArraySet<>();

    // Data Saver模式开关
    private boolean mRestrictBackground = false;

    // 核心方法1:设置UID网络策略
    @Override
    public void setUidPolicy(int uid, int policy) {
        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);

        // 1. 更新策略
        if (policy == POLICY_NONE) {
            mUidPolicy.delete(uid);
        } else {
            mUidPolicy.put(uid, policy);
        }

        // 2. 更新防火墙规则
        updateRulesForDataUsageRestrictionsUL(uid);

        // 3. 持久化到磁盘
        writePolicyAL();

        // 4. 通知监听器
        mHandler.obtainMessage(MSG_POLICIES_CHANGED, uid, policy).sendToTarget();
    }

    // 核心方法2:更新防火墙规则
    private void updateRulesForDataUsageRestrictionsUL(int uid) {
        // 计算最终的网络规则
        final int uidRules = mUidRules.get(uid, RULE_NONE);
        final int oldUidRules = mUidRules.get(uid, RULE_NONE);

        // 规则改变才更新
        if (uidRules == oldUidRules) return;

        // 更新规则到Kernel
        setUidFirewallRulesUL(FIREWALL_CHAIN_STANDBY, uid,
                              uidRules & RULE_REJECT_METERED);
        setUidFirewallRulesUL(FIREWALL_CHAIN_DOZABLE, uid,
                              uidRules & RULE_REJECT_ALL);
        setUidFirewallRulesUL(FIREWALL_CHAIN_POWERSAVE, uid,
                              uidRules & RULE_REJECT_ALL);

        // Android 15: 使用eBPF代替iptables
        if (mUseBpfTrafficStats) {
            mBpfNetMaps.setUidRule(uid, uidRules);
        }
    }

    // 核心方法3:设置Data Saver模式
    @Override
    public void setRestrictBackground(boolean restrictBackground) {
        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);

        if (mRestrictBackground == restrictBackground) return;

        mRestrictBackground = restrictBackground;

        // 更新所有应用的网络规则
        updateRulesForRestrictBackgroundUL();

        // 通知系统服务
        mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_CHANGED,
                               restrictBackground ? 1 : 0, 0).sendToTarget();
    }
}

1.2 Data Saver省流模式详解

Data Saver是Android 7.0引入的省流功能,在Android 15中得到了eBPF的性能增强。

工作原理

用户开启Data Saver后:

1. 后台应用网络被禁用
   → 除非应用在白名单中

2. 前台应用可以正常联网
   → 但会收到DATA_SAVER_ENABLED回调

3. 应用可以请求白名单权限
   → POLICY_ALLOW_METERED_BACKGROUND

4. 系统服务不受影响
   → UID < 10000的系统进程豁免

应用示例

// 应用层 - 检测Data Saver状态
public class DataSaverChecker {

    private ConnectivityManager mCm;

    public void checkDataSaver(Context context) {
        mCm = (ConnectivityManager) context.getSystemService(
            Context.CONNECTIVITY_SERVICE);

        // 1. 获取Data Saver状态
        int status = mCm.getRestrictBackgroundStatus();

        switch (status) {
            case RESTRICT_BACKGROUND_STATUS_DISABLED:
                // Data Saver关闭,无限制
                Log.d(TAG, "Data Saver关闭");
                break;

            case RESTRICT_BACKGROUND_STATUS_ENABLED:
                // Data Saver开启,后台数据被禁用
                Log.w(TAG, "Data Saver开启,后台数据受限");
                // 建议:延迟非紧急的网络请求
                postponeNonUrgentRequests();
                break;

            case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
                // 应用在白名单中,不受限制
                Log.d(TAG, "应用在Data Saver白名单");
                break;
        }
    }

    // 2. 监听Data Saver变化
    public void registerDataSaverListener(Context context) {
        mCm.registerDefaultNetworkCallback(new NetworkCallback() {
            @Override
            public void onCapabilitiesChanged(Network network,
                                             NetworkCapabilities nc) {
                // 检查是否有NOT_METERED能力
                boolean isMetered = !nc.hasCapability(
                    NET_CAPABILITY_NOT_METERED);

                if (isMetered && isDataSaverEnabled()) {
                    // 在按流量计费网络 + Data Saver开启
                    showDataSaverWarning();
                }
            }
        });
    }

    // 3. 应用优化建议
    private void optimizeForDataSaver() {
        if (isDataSaverEnabled()) {
            // 减少预加载
            disablePrefetching();

            // 降低图片质量
            setImageQuality(QUALITY_LOW);

            // 禁用自动播放视频
            disableAutoplayVideos();

            // 延迟大文件下载
            pauseLargeDownloads();
        }
    }
}

Framework层实现

// NetworkPolicyManagerService.java
private void updateRulesForRestrictBackgroundUL() {
    // 遍历所有UID
    final int size = mUidPolicy.size();
    for (int i = 0; i < size; i++) {
        final int uid = mUidPolicy.keyAt(i);
        final int policy = mUidPolicy.valueAt(i);

        // 计算规则
        int uidRules = RULE_NONE;
        if (mRestrictBackground) {
            // Data Saver开启
            if (!hasWhitelistExemption(policy)) {
                // 不在白名单,禁止后台数据
                uidRules |= RULE_REJECT_METERED;
            }
        }

        // 应用规则
        setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, uidRules);
    }
}

// 检查白名单
private boolean hasWhitelistExemption(int policy) {
    // 允许按流量计费网络的后台数据
    return (policy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
}

1.3 流量统计NetworkStatsService

核心功能:实时统计应用和网络接口的流量数据。

// packages/modules/Connectivity/.../NetworkStatsService.java
public class NetworkStatsService extends INetworkStatsService.Stub {

    // UID维度统计
    private final NetworkStatsRecorder mUidRecorder;

    // Interface维度统计
    private final NetworkStatsRecorder mUidTagRecorder;

    // 核心方法:获取UID流量统计
    @Override
    public NetworkStats getUidStats(int uid, NetworkTemplate template) {
        // 1. 从BPF读取统计数据 (Android 15)
        NetworkStats stats = mBpfNetMaps.readStatsForUid(uid);

        // 2. 合并历史数据
        stats.combineWith(mUidRecorder.getOrLoadHistoryFor(uid, template));

        // 3. 返回结果
        return stats;
    }

    // 轮询更新统计数据
    private void performPoll(int flags) {
        // 1. 从Kernel读取最新数据
        final NetworkStats xtSnapshot = readNetworkStatsUidDetail(UID_ALL);

        // 2. 计算增量
        final NetworkStats delta = xtSnapshot.subtract(mLastStatsSnapshot);
        mLastStatsSnapshot = xtSnapshot;

        // 3. 记录到历史
        mUidRecorder.recordSnapshotLocked(delta, System.currentTimeMillis());

        // 4. 检查流量配额
        mPolicyListener.onStatsUpdated(delta);
    }
}

应用层使用TrafficStats

// 查询应用流量
public class TrafficStatsExample {

    public void showAppTraffic(int uid) {
        // 1. 获取接收字节数
        long rxBytes = TrafficStats.getUidRxBytes(uid);

        // 2. 获取发送字节数
        long txBytes = TrafficStats.getUidTxBytes(uid);

        // 3. 获取接收数据包数
        long rxPackets = TrafficStats.getUidRxPackets(uid);

        // 4. 获取发送数据包数
        long txPackets = TrafficStats.getUidTxPackets(uid);

        Log.d(TAG, String.format(
            "UID %d: RX=%s TX=%s",
            uid,
            humanReadableByteCount(rxBytes),
            humanReadableByteCount(txBytes)
        ));
    }

    // 标记Socket (用于细粒度统计)
    public void tagSocket(Socket socket, int tag) {
        // 设置线程的流量标签
        TrafficStats.setThreadStatsTag(tag);

        try {
            // 标记Socket
            TrafficStats.tagSocket(socket);

            // 进行网络操作...
            socket.connect(address);

        } finally {
            // 取消标签
            TrafficStats.untagSocket(socket);
            TrafficStats.clearThreadStatsTag();
        }
    }

    // 格式化流量显示
    private String humanReadableByteCount(long bytes) {
        if (bytes < 1024) return bytes + " B";
        int exp = (int) (Math.log(bytes) / Math.log(1024));
        char pre = "KMGTPE".charAt(exp - 1);
        return String.format("%.1f %sB",
                           bytes / Math.pow(1024, exp), pre);
    }
}

1.4 按流量计费网络识别

Android需要识别哪些网络是按流量计费的(Metered Network),以便应用优化行为。

// NetworkCapabilities中的能力标识
public class NetworkCapabilities {
    // 非计费网络(如家庭WiFi)
    public static final int NET_CAPABILITY_NOT_METERED = 11;

    // 临时非计费(如运营商免费流量)
    public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25;
}

识别逻辑

// ConnectivityService.java
private void updateMeteredStatus(NetworkAgentInfo nai) {
    NetworkCapabilities nc = nai.networkCapabilities;

    // 1. WiFi默认非计费
    if (nc.hasTransport(TRANSPORT_WIFI)) {
        nc.addCapability(NET_CAPABILITY_NOT_METERED);
    }

    // 2. 移动网络默认计费
    if (nc.hasTransport(TRANSPORT_CELLULAR)) {
        nc.removeCapability(NET_CAPABILITY_NOT_METERED);
    }

    // 3. 但可以被配置覆盖
    if (mNetworkPolicies.isNetworkMetered(nai)) {
        nc.removeCapability(NET_CAPABILITY_NOT_METERED);
    }
}

应用检查示例

public boolean isCurrentNetworkMetered(Context context) {
    ConnectivityManager cm = (ConnectivityManager)
        context.getSystemService(Context.CONNECTIVITY_SERVICE);

    // 1. 获取当前网络
    Network network = cm.getActiveNetwork();
    if (network == null) return true; // 安全起见,假设计费

    // 2. 获取能力
    NetworkCapabilities capabilities = cm.getNetworkCapabilities(network);
    if (capabilities == null) return true;

    // 3. 检查是否非计费
    return !capabilities.hasCapability(NET_CAPABILITY_NOT_METERED);
}

1.5 Android 15新特性:eBPF网络策略

Android 15使用**eBPF (extended Berkeley Packet Filter)**替代传统的iptables,性能提升显著。

eBPF vs iptables对比

特性iptableseBPF (Android 15)
规则匹配线性扫描 O(n)哈希查找 O(1)
延迟~100μs~10μs
内核态切换需要不需要
动态更新锁全表无锁更新
CPU占用

eBPF实现原理

// system/netd/bpf_progs/netd.c

// BPF程序 - UID流量控制
SEC("cgroupskb/ingress/stats")
int bpf_traffic_account(struct __sk_buff *skb) {
    // 1. 获取UID
    uint32_t uid = bpf_get_socket_uid(skb);

    // 2. 查询UID规则
    uint8_t *rule = bpf_uid_permission_map_lookup_elem(&uid);

    // 3. 检查权限
    if (rule && (*rule & BPF_PERMISSION_INTERNET) == 0) {
        return 0;  // 拒绝:丢弃数据包
    }

    // 4. 统计流量
    struct stats_value *value = bpf_uid_stats_map_lookup_elem(&uid);
    if (value) {
        __sync_fetch_and_add(&value->rxBytes, skb->len);
        __sync_fetch_and_add(&value->rxPackets, 1);
    }

    return 1;  // 允许:继续传输
}

// BPF Map - UID权限表
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, uint32_t);    // UID
    __type(value, uint8_t);   // 权限标志
    __uint(max_entries, 10000);
} uid_permission_map SEC(".maps");

// BPF Map - UID流量统计
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, uint32_t);    // UID
    __type(value, struct stats_value);
    __uint(max_entries, 10000);
} uid_stats_map SEC(".maps");

Java层接口

// frameworks/base/.../BpfNetMaps.java
public class BpfNetMaps {

    // 设置UID网络规则
    public void setUidRule(int uid, int rule) {
        // 直接更新BPF Map
        mUidPermissionMap.updateEntry(
            new UidPermissionKey(uid),
            new UidPermissionValue(rule)
        );
    }

    // 读取UID流量统计
    public NetworkStats readStatsForUid(int uid) {
        UidStatsValue value = mUidStatsMap.getValue(
            new UidStatsKey(uid)
        );

        return new NetworkStats.Entry(
            null, uid,
            SET_DEFAULT, TAG_NONE,
            METERED_NO, ROAMING_NO,
            DEFAULT_NETWORK_NO,
            value.rxBytes, value.rxPackets,
            value.txBytes, value.txPackets,
            0 /* operations */
        );
    }
}

性能提升

测试场景: 1000个应用同时联网

iptables方式:
  - 规则匹配时间: 1000 * 100μs = 100ms
  - CPU占用: 15%

eBPF方式:
  - 规则匹配时间: 1000 * 10μs = 10ms
  - CPU占用: 3%

性能提升: 10倍延迟降低,5倍CPU减少

二、VPN框架深度解析

2.1 VpnService架构总览

VPN (Virtual Private Network) 虚拟专用网络,在Android中通过VpnService框架实现。

08-02-vpn-workflow.png

核心组件

┌─────────────────────────────────────────┐
│          Android VPN架构                 │
├─────────────────────────────────────────┤
│                                         │
│  应用层                                  │
│  ┌────────────────┐                     │
│  │  VpnService    │ ← 应用继承实现      │
│  │  (抽象基类)     │                     │
│  └────────────────┘                     │
│         ↓                                │
│  ┌────────────────┐                     │
│  │  Builder       │ ← 配置VPN参数       │
│  └────────────────┘                     │
│                                         │
│  Framework层                             │
│  ┌────────────────┐                     │
│  │  Vpn.java      │ ← VPN管理核心       │
│  └────────────────┘                     │
│         ↓                                │
│  ┌────────────────┐                     │
│  │ConnectivitySrv │ ← 网络连接管理      │
│  └────────────────┘                     │
│                                         │
│  Native层                                │
│  ┌────────────────┐                     │
│  │  Netd          │ ← 路由配置          │
│  └────────────────┘                     │
│                                         │
│  Kernel层                                │
│  ┌────────────────┐                     │
│  │  TUN Driver    │ ← 虚拟网卡          │
│  │  /dev/tun      │                     │
│  └────────────────┘                     │
│                                         │
└─────────────────────────────────────────┘

2.2 VpnService应用层实现

基础VPN示例

// 应用实现VPN服务
public class MyVpnService extends VpnService {

    private ParcelFileDescriptor mInterface;
    private Thread mVpnThread;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 1. 准备VPN (弹出用户授权对话框)
        Intent prepare = VpnService.prepare(this);
        if (prepare != null) {
            // 需要用户授权
            startActivity(prepare);
            return START_NOT_STICKY;
        }

        // 2. 建立VPN连接
        mVpnThread = new Thread(this::setupVpn);
        mVpnThread.start();

        return START_STICKY;
    }

    private void setupVpn() {
        try {
            // 3. 配置VPN参数
            Builder builder = new Builder();
            builder
                // 设置VPN地址
                .addAddress("10.0.0.2", 24)

                // 设置路由 (所有流量通过VPN)
                .addRoute("0.0.0.0", 0)

                // 设置DNS
                .addDnsServer("8.8.8.8")
                .addDnsServer("8.8.4.4")

                // 设置MTU
                .setMtu(1500)

                // 设置会话名称
                .setSession("MyVPN")

                // 配置允许/禁止的应用
                .addAllowedApplication("com.example.app")
                .addDisallowedApplication("com.example.excluded");

            // 4. 建立TUN接口
            mInterface = builder.establish();
            if (mInterface == null) {
                Log.e(TAG, "Failed to establish VPN");
                return;
            }

            // 5. 获取文件描述符
            FileDescriptor fd = mInterface.getFileDescriptor();

            // 6. 读写TUN设备
            handleTraffic(fd);

        } catch (Exception e) {
            Log.e(TAG, "VPN setup failed", e);
        }
    }

    // 处理TUN设备流量
    private void handleTraffic(FileDescriptor fd) {
        FileInputStream in = new FileInputStream(fd);
        FileOutputStream out = new FileOutputStream(fd);

        byte[] packet = new byte[32767];

        while (true) {
            try {
                // 1. 从TUN设备读取IP数据包
                int length = in.read(packet);
                if (length > 0) {
                    // 2. 解析IP包
                    IpPacket ipPacket = parseIpPacket(packet, length);

                    // 3. 加密/封装数据
                    byte[] encrypted = encrypt(ipPacket);

                    // 4. 发送到VPN服务器
                    sendToVpnServer(encrypted);
                }

                // 5. 从VPN服务器接收数据
                byte[] received = receiveFromVpnServer();
                if (received != null) {
                    // 6. 解密/解封装
                    byte[] decrypted = decrypt(received);

                    // 7. 写回TUN设备
                    out.write(decrypted);
                }

            } catch (IOException e) {
                break;
            }
        }
    }

    @Override
    public void onDestroy() {
        if (mVpnThread != null) {
            mVpnThread.interrupt();
        }
        if (mInterface != null) {
            try {
                mInterface.close();
            } catch (IOException e) {
                // Ignore
            }
        }
        super.onDestroy();
    }
}

VPN配置详解

public class VpnConfigExample {

    // 配置1:全局VPN (所有流量)
    public ParcelFileDescriptor setupGlobalVpn(VpnService service) {
        return service.new Builder()
            .addAddress("10.0.0.2", 24)
            .addRoute("0.0.0.0", 0)      // 所有IPv4流量
            .addRoute("::", 0)           // 所有IPv6流量
            .addDnsServer("8.8.8.8")
            .establish();
    }

    // 配置2:分流VPN (仅特定网段)
    public ParcelFileDescriptor setupSplitVpn(VpnService service) {
        return service.new Builder()
            .addAddress("10.0.0.2", 24)
            .addRoute("192.168.0.0", 16)   // 仅企业内网
            .addRoute("10.0.0.0", 8)       // 仅私有网络
            .addDnsServer("10.0.0.1")
            .establish();
    }

    // 配置3:应用级VPN (仅特定应用)
    public ParcelFileDescriptor setupPerAppVpn(VpnService service)
            throws PackageManager.NameNotFoundException {
        Builder builder = service.new Builder()
            .addAddress("10.0.0.2", 24)
            .addRoute("0.0.0.0", 0);

        // 方式1:白名单模式 (只有指定应用走VPN)
        builder.addAllowedApplication("com.example.secure");
        builder.addAllowedApplication("com.example.banking");

        // 方式2:黑名单模式 (指定应用不走VPN)
        // builder.addDisallowedApplication("com.example.local");

        return builder.establish();
    }

    // 配置4:Always-on VPN (系统级持久VPN)
    public ParcelFileDescriptor setupAlwaysOnVpn(VpnService service) {
        return service.new Builder()
            .addAddress("10.0.0.2", 24)
            .addRoute("0.0.0.0", 0)
            .addDnsServer("8.8.8.8")
            .setBlocking(true)  // 阻塞模式:VPN断开时阻止所有流量
            .establish();
    }
}

2.3 Framework层VPN管理

源码位置

packages/modules/Connectivity/service/src/com/android/server/connectivity/Vpn.java

Vpn.java核心实现

// Vpn.java (简化版)
public class Vpn {

    // VPN配置
    private VpnConfig mConfig;

    // TUN接口名称
    private String mInterface;

    // VPN网络
    private Network mNetwork;

    // VPN NetworkAgent
    private NetworkAgent mNetworkAgent;

    // 核心方法:建立VPN
    public synchronized ParcelFileDescriptor establish(VpnConfig config) {
        // 1. 权限检查
        enforceControlPermission();

        // 2. 创建TUN设备
        String interfaceName = jniCreate(config.mtu);
        if (interfaceName == null) {
            throw new IllegalStateException("Failed to create TUN device");
        }
        mInterface = interfaceName;

        // 3. 配置TUN设备地址
        for (LinkAddress address : config.addresses) {
            jniAddAddress(interfaceName, address.getAddress(),
                         address.getPrefixLength());
        }

        // 4. 配置路由
        Connection connection = new Connection();
        connection.interfaceName = interfaceName;
        connection.routes = config.routes;

        // 5. 通知Netd配置路由
        try {
            mNetd.addVpnUidRanges(mInterface, config.allowedUidRanges);

            for (RouteInfo route : config.routes) {
                mNetd.vpnAddRoute(mInterface, route);
            }
        } catch (RemoteException e) {
            throw new IllegalStateException("Netd error", e);
        }

        // 6. 设置DNS
        try {
            mNetd.setDnsServersForInterface(interfaceName,
                                           config.dnsServers,
                                           config.searchDomains);
        } catch (RemoteException e) {
            // ...
        }

        // 7. 创建NetworkAgent
        agentConnect(config);

        // 8. 打开TUN设备文件描述符
        ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(
            jniGetFd(interfaceName)
        );

        return tun;
    }

    // 创建NetworkAgent
    private void agentConnect(VpnConfig config) {
        // 构建NetworkCapabilities
        NetworkCapabilities caps = new NetworkCapabilities();
        caps.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
        caps.removeCapability(NET_CAPABILITY_NOT_VPN);

        if (config.allowBypass) {
            caps.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
        }

        // 构建LinkProperties
        LinkProperties lp = new LinkProperties();
        lp.setInterfaceName(mInterface);

        // 创建NetworkAgent
        mNetworkAgent = new NetworkAgent(mContext, mLooper,
            "VPN", caps, lp, 0, new NetworkAgentConfig(), this) {

            @Override
            public void onNetworkUnwanted() {
                // VPN被移除
                Vpn.this.onNetworkUnwanted();
            }
        };

        mNetworkAgent.register();
        mNetwork = mNetworkAgent.getNetwork();
    }

    // JNI方法 - 创建TUN设备
    private native int jniCreate(int mtu);
    private native String jniGetName(int tun);
    private native void jniAddAddress(String interfaceName,
                                     String address, int prefixLen);
    private native int jniGetFd(String interfaceName);
}

JNI层TUN设备操作

// frameworks/base/core/jni/android_net_LocalSocketImpl.cpp

static jint com_android_server_connectivity_Vpn_jniCreate(JNIEnv* env,
                                                          jobject /* clazz */,
                                                          jint mtu) {
    // 1. 打开TUN设备
    int tun = open("/dev/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC);
    if (tun < 0) {
        jniThrowException(env, "java/io/IOException",
                         "Cannot open TUN device");
        return -1;
    }

    // 2. 配置TUN设备
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TUN | IFF_NO_PI;  // TUN模式,无协议头
    strncpy(ifr.ifr_name, "tun%d", IFNAMSIZ);

    // 3. 创建TUN接口
    if (ioctl(tun, TUNSETIFF, &ifr) < 0) {
        close(tun);
        jniThrowException(env, "java/io/IOException",
                         "Cannot configure TUN device");
        return -1;
    }

    // 4. 设置MTU
    if (mtu > 0 && mtu != ifr.ifr_mtu) {
        ifr.ifr_mtu = mtu;
        if (ioctl(tun, SIOCSIFMTU, &ifr) < 0) {
            ALOGW("Cannot set MTU");
        }
    }

    return tun;
}

// 添加IP地址到TUN接口
static void com_android_server_connectivity_Vpn_jniAddAddress(
        JNIEnv* env, jobject /* clazz */,
        jstring jInterfaceName, jstring jAddress, jint prefixLen) {

    const char* interfaceName = env->GetStringUTFChars(jInterfaceName, NULL);
    const char* address = env->GetStringUTFChars(jAddress, NULL);

    // 使用netlink添加地址
    struct {
        struct nlmsghdr n;
        struct ifaddrmsg ifa;
        char buf[256];
    } req;

    memset(&req, 0, sizeof(req));
    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
    req.n.nlmsg_type = RTM_NEWADDR;
    req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;

    req.ifa.ifa_family = AF_INET;  // 或AF_INET6
    req.ifa.ifa_prefixlen = prefixLen;
    req.ifa.ifa_index = if_nametoindex(interfaceName);

    // 添加地址属性...
    addattr_l(&req.n, sizeof(req), IFA_LOCAL, address, 4);

    // 发送到内核...
    send_netlink_message(&req.n);

    env->ReleaseStringUTFChars(jInterfaceName, interfaceName);
    env->ReleaseStringUTFChars(jAddress, address);
}

2.4 Always-on VPN

Always-on VPN是Android 7.0引入的功能,确保设备始终通过VPN连接。

工作机制

Always-on VPN开启后:

1. 系统启动时自动启动VPN
   → 无需用户手动连接

2. VPN断开时阻止所有网络访问
   → "Block connections without VPN"选项

3. VPN服务崩溃时自动重连
   → 由系统监控并重启

4. 不允许用户手动断开
   → 只有管理员可以关闭

配置Always-on VPN

// 系统设置 - 配置Always-on VPN
public class AlwaysOnVpnConfig {

    // 设置Always-on VPN
    public void setAlwaysOnVpn(Context context, String packageName,
                               boolean lockdownEnabled) {
        ConnectivityManager cm = context.getSystemService(
            ConnectivityManager.class);

        // 需要CONTROL_ALWAYS_ON_VPN权限 (系统应用)
        cm.setAlwaysOnVpnPackageForUser(
            UserHandle.myUserId(),
            packageName,
            lockdownEnabled,    // 是否阻断模式
            null                // 允许绕过的应用列表
        );
    }

    // 检查Always-on VPN状态
    public boolean isAlwaysOnVpnEnabled(Context context) {
        ConnectivityManager cm = context.getSystemService(
            ConnectivityManager.class);

        String vpnPackage = cm.getAlwaysOnVpnPackageForUser(
            UserHandle.myUserId()
        );

        return vpnPackage != null;
    }
}

Framework实现

// Vpn.java
public boolean setAlwaysOnPackage(String packageName, boolean lockdown,
                                  List<String> lockdownWhitelist) {
    enforceControlPermissionOrInternalCaller();

    if (packageName == null) {
        // 关闭Always-on VPN
        mAlwaysOn = false;
        mLockdown = false;
        return true;
    }

    // 验证VPN应用
    if (!isVpnServicePreConsented(packageName)) {
        return false;
    }

    // 保存配置
    mAlwaysOn = true;
    mLockdown = lockdown;
    mLockdownWhitelist = lockdownWhitelist;

    // 启动VPN
    startAlwaysOnVpn();

    return true;
}

private void startAlwaysOnVpn() {
    // 1. 如果已连接,什么都不做
    if (mNetworkAgent != null && mNetworkAgent.isConnected()) {
        return;
    }

    // 2. 启动VPN服务
    Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
    intent.setPackage(mPackage);
    intent.setComponent(mPackage, mService);

    try {
        mContext.startServiceAsUser(intent, UserHandle.of(mUserHandle));
    } catch (RuntimeException e) {
        Log.e(TAG, "Failed to start Always-on VPN", e);
    }

    // 3. 如果是锁定模式,配置防火墙
    if (mLockdown) {
        setVpnForcedLocked(true);
    }
}

// 锁定模式:阻止所有非VPN流量
private void setVpnForcedLocked(boolean enforce) {
    if (enforce) {
        // 添加防火墙规则:拒绝所有UID的网络访问
        // 除了VPN应用和白名单应用
        mNetd.firewallSetUidRule(FIREWALL_CHAIN_VPN, UID_ALL, DENY);

        // 允许VPN应用
        mNetd.firewallSetUidRule(FIREWALL_CHAIN_VPN,
                                mOwnerUID, ALLOW);

        // 允许白名单应用
        for (String pkg : mLockdownWhitelist) {
            int uid = getUidForPackage(pkg);
            mNetd.firewallSetUidRule(FIREWALL_CHAIN_VPN, uid, ALLOW);
        }
    } else {
        // 移除防火墙规则
        mNetd.firewallSetUidRule(FIREWALL_CHAIN_VPN, UID_ALL, ALLOW);
    }
}

三、网络性能优化实战

3.1 流量优化技巧

1. 压缩传输

public class NetworkOptimization {

    // 使用GZIP压缩HTTP请求体
    public void sendCompressedRequest(String url, String data)
            throws IOException {
        HttpURLConnection conn = (HttpURLConnection)
            new URL(url).openConnection();

        conn.setRequestMethod("POST");
        conn.setDoOutput(true);
        conn.setRequestProperty("Content-Encoding", "gzip");

        // GZIP压缩
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (GZIPOutputStream gzos = new GZIPOutputStream(baos)) {
            gzos.write(data.getBytes(StandardCharsets.UTF_8));
        }
        byte[] compressed = baos.toByteArray();

        // 发送压缩数据
        try (OutputStream os = conn.getOutputStream()) {
            os.write(compressed);
        }

        // 日志压缩比
        Log.d(TAG, String.format("Compression ratio: %.2f%%",
            100.0 * compressed.length / data.length()));
    }

    // 使用Brotli压缩 (更高压缩率)
    public void sendBrotliRequest(String url, byte[] data)
            throws IOException {
        // Brotli库: com.aayushatharva.brotli4j:brotli4j

        BrotliCompressor compressor = new BrotliCompressor();
        byte[] compressed = compressor.compress(data,
            Brotli.DEFAULT_QUALITY, Brotli.DEFAULT_WINDOW);

        HttpURLConnection conn = (HttpURLConnection)
            new URL(url).openConnection();
        conn.setRequestProperty("Content-Encoding", "br");

        // 发送...
    }
}

2. 图片优化

public class ImageOptimization {

    // 根据网络状态调整图片质量
    public void loadOptimizedImage(Context context, String url,
                                   ImageView imageView) {
        // 1. 检查网络类型
        boolean isMetered = isCurrentNetworkMetered(context);
        boolean isSlowNetwork = isSlowNetwork(context);

        // 2. 选择质量
        String imageUrl;
        if (isSlowNetwork || isMetered) {
            // 慢速/计费网络:低质量图片
            imageUrl = url + "?quality=low&size=small";
        } else {
            // 快速/非计费网络:高质量图片
            imageUrl = url + "?quality=high&size=large";
        }

        // 3. 加载图片 (使用Glide/Coil等库)
        Glide.with(context)
            .load(imageUrl)
            .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
            .into(imageView);
    }

    // 判断是否慢速网络
    private boolean isSlowNetwork(Context context) {
        ConnectivityManager cm = context.getSystemService(
            ConnectivityManager.class);

        NetworkCapabilities caps = cm.getNetworkCapabilities(
            cm.getActiveNetwork());

        if (caps == null) return true;

        // 下行带宽 < 1Mbps 视为慢速
        int downBandwidth = caps.getLinkDownstreamBandwidthKbps();
        return downBandwidth < 1000;
    }
}

3. 预取策略

public class PrefetchStrategy {

    // 智能预取
    public void smartPrefetch(Context context) {
        // 仅在WiFi + 充电时预取
        if (isWifiConnected(context) && isCharging(context)) {
            // 预取下一章节内容
            prefetchNextChapter();

            // 预加载推荐列表
            prefetchRecommendations();

            // 同步离线数据
            syncOfflineData();
        }
    }

    private boolean isWifiConnected(Context context) {
        ConnectivityManager cm = context.getSystemService(
            ConnectivityManager.class);

        NetworkCapabilities caps = cm.getNetworkCapabilities(
            cm.getActiveNetwork());

        return caps != null &&
               caps.hasTransport(TRANSPORT_WIFI) &&
               caps.hasCapability(NET_CAPABILITY_NOT_METERED);
    }

    private boolean isCharging(Context context) {
        BatteryManager bm = context.getSystemService(
            BatteryManager.class);

        return bm.isCharging();
    }
}

3.2 DNS优化

1. DNS缓存

public class DnsCache {

    private final Map<String, InetAddress[]> mCache =
        new ConcurrentHashMap<>();

    // DNS查询带缓存
    public InetAddress[] lookup(String hostname) throws UnknownHostException {
        // 1. 检查缓存
        InetAddress[] cached = mCache.get(hostname);
        if (cached != null && !isExpired(cached)) {
            return cached;
        }

        // 2. DNS查询
        InetAddress[] addresses = InetAddress.getAllByName(hostname);

        // 3. 缓存结果 (TTL 5分钟)
        mCache.put(hostname, addresses);
        scheduleExpiry(hostname, 5 * 60 * 1000);

        return addresses;
    }

    // 预热DNS缓存
    public void warmUpCache(String... hostnames) {
        ExecutorService executor = Executors.newFixedThreadPool(4);

        for (String hostname : hostnames) {
            executor.submit(() -> {
                try {
                    lookup(hostname);
                } catch (UnknownHostException e) {
                    // Ignore
                }
            });
        }
    }
}

2. DoH (DNS over HTTPS)

public class DohResolver {

    private static final String DOH_SERVER =
        "https://dns.google/dns-query";

    // 使用DoH查询DNS
    public List<InetAddress> resolveDoH(String hostname)
            throws IOException {
        // 1. 构建DoH请求
        String url = DOH_SERVER + "?name=" + hostname + "&type=A";

        HttpsURLConnection conn = (HttpsURLConnection)
            new URL(url).openConnection();
        conn.setRequestProperty("Accept", "application/dns-json");

        // 2. 发送请求
        int responseCode = conn.getResponseCode();
        if (responseCode != 200) {
            throw new IOException("DoH query failed: " + responseCode);
        }

        // 3. 解析JSON响应
        String response = readStream(conn.getInputStream());
        JSONObject json = new JSONObject(response);

        JSONArray answers = json.getJSONArray("Answer");
        List<InetAddress> addresses = new ArrayList<>();

        for (int i = 0; i < answers.length(); i++) {
            JSONObject answer = answers.getJSONObject(i);
            String ip = answer.getString("data");
            addresses.add(InetAddress.getByName(ip));
        }

        return addresses;
    }
}

3.3 连接池管理

public class ConnectionPoolOptimization {

    // OkHttp连接池配置
    public OkHttpClient createOptimizedClient() {
        // 1. 配置连接池
        ConnectionPool connectionPool = new ConnectionPool(
            10,              // 最大空闲连接数
            5,               // 连接保活时间
            TimeUnit.MINUTES
        );

        // 2. 配置超时
        return new OkHttpClient.Builder()
            .connectionPool(connectionPool)
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)

            // 3. 启用HTTP/2
            .protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))

            // 4. 配置重试
            .retryOnConnectionFailure(true)

            // 5. 添加拦截器
            .addInterceptor(new LoggingInterceptor())
            .addNetworkInterceptor(new CacheInterceptor())

            .build();
    }

    // 自定义缓存拦截器
    private class CacheInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();

            // 离线时使用缓存
            if (!isNetworkAvailable()) {
                request = request.newBuilder()
                    .cacheControl(CacheControl.FORCE_CACHE)
                    .build();
            }

            Response response = chain.proceed(request);

            // 自定义缓存策略
            if (isNetworkAvailable()) {
                // 在线:缓存1小时
                response = response.newBuilder()
                    .header("Cache-Control",
                           "public, max-age=" + 60 * 60)
                    .build();
            } else {
                // 离线:缓存4周
                response = response.newBuilder()
                    .header("Cache-Control",
                           "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 28)
                    .build();
            }

            return response;
        }
    }
}

3.4 网络诊断工具

1. 网络质量监测

# 检查网络连接状态
adb shell dumpsys connectivity | grep "Active networks"

# 检查DNS配置
adb shell getprop net.dns1
adb shell getprop net.dns2

# 测试网络延迟
adb shell ping -c 4 8.8.8.8

# 测试DNS解析
adb shell nslookup www.google.com

# 检查路由表
adb shell ip route show

# 查看接口统计
adb shell ifconfig wlan0

# 抓取网络数据包
adb shell tcpdump -i wlan0 -w /sdcard/capture.pcap

# 查看iptables规则
adb shell iptables -L -n -v

# 查看eBPF统计 (Android 15)
adb shell dumpsys connectivity trafficcontroller

2. 流量监控

public class TrafficMonitor {

    private long mLastRxBytes = 0;
    private long mLastTxBytes = 0;
    private long mLastTime = 0;

    // 计算实时网速
    public String getCurrentNetworkSpeed(int uid) {
        long currentTime = System.currentTimeMillis();
        long currentRx = TrafficStats.getUidRxBytes(uid);
        long currentTx = TrafficStats.getUidTxBytes(uid);

        if (mLastTime == 0) {
            // 首次调用,初始化
            mLastRxBytes = currentRx;
            mLastTxBytes = currentTx;
            mLastTime = currentTime;
            return "0 KB/s";
        }

        // 计算时间间隔
        long timeDelta = currentTime - mLastTime;
        if (timeDelta == 0) return "0 KB/s";

        // 计算流量增量
        long rxDelta = currentRx - mLastRxBytes;
        long txDelta = currentTx - mLastTxBytes;

        // 计算速度 (字节/秒)
        long rxSpeed = rxDelta * 1000 / timeDelta;
        long txSpeed = txDelta * 1000 / timeDelta;

        // 更新记录
        mLastRxBytes = currentRx;
        mLastTxBytes = currentTx;
        mLastTime = currentTime;

        return String.format("↓ %s/s ↑ %s/s",
            formatBytes(rxSpeed),
            formatBytes(txSpeed));
    }

    private String formatBytes(long bytes) {
        if (bytes < 1024) return bytes + " B";
        if (bytes < 1024 * 1024) return (bytes / 1024) + " KB";
        return String.format("%.2f MB", bytes / 1024.0 / 1024.0);
    }
}

四、Android 15新特性

4.1 网络切片支持(Network Slicing)

5G网络切片允许为不同业务提供差异化的网络服务。

// Android 15 - 请求企业专网切片
public void requestEnterpriseSlice(Context context) {
    ConnectivityManager cm = context.getSystemService(
        ConnectivityManager.class);

    NetworkRequest request = new NetworkRequest.Builder()
        .addCapability(NET_CAPABILITY_ENTERPRISE)
        .setEnterpriseId(NET_ENTERPRISE_ID_1)  // 企业切片ID
        .build();

    cm.requestNetwork(request, new NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            // 使用企业专网切片
            // - 低延迟 (<10ms)
            // - 高带宽保证
            // - QoS优先级高
            bindProcessToNetwork(network);
        }
    });
}

4.2 卫星连接支持

// Android 15 - 检测卫星网络
public boolean isSatelliteNetwork(Context context) {
    ConnectivityManager cm = context.getSystemService(
        ConnectivityManager.class);

    NetworkCapabilities caps = cm.getNetworkCapabilities(
        cm.getActiveNetwork());

    return caps != null &&
           caps.hasTransport(TRANSPORT_SATELLITE);
}

4.3 eBPF性能提升

Android 15全面使用eBPF替代iptables:

  • 流量统计:O(1)复杂度
  • 防火墙规则:哈希查找,延迟降低90%
  • CPU占用:减少60%

五、总结与展望

5.1 核心要点回顾

  1. NetworkPolicy流量控制

    • NetworkPolicyManagerService管理网络策略
    • Data Saver省流模式
    • 流量统计与限制
    • eBPF性能优化
  2. VPN框架

    • VpnService应用层实现
    • TUN虚拟网卡
    • 路由配置与数据转发
    • Always-on VPN机制
  3. 网络优化

    • 压缩传输
    • DNS缓存与DoH
    • 连接池管理
    • 流量监控工具

5.2 最佳实践建议

场景建议
省流优化监听Data Saver状态,动态调整资源质量
VPN开发使用TUN而非TAP,处理好异常重连
后台同步使用WorkManager + 网络约束条件
大文件下载分段下载,支持断点续传,WiFi优先
实时通信使用WebSocket保持长连接,心跳保活

六、参考资源

6.1 源码路径

# NetworkPolicy
packages/modules/Connectivity/service/src/com/android/server/net/
├── NetworkPolicyManagerService.java
├── NetworkStatsService.java
└── NetworkPolicyManagerInternal.java

# VPN
packages/modules/Connectivity/service/src/com/android/server/connectivity/
├── Vpn.java
└── VpnNetworkObserver.java

# eBPF
system/netd/bpf_progs/
├── netd.c
└── bpf_net_helpers.h

6.2 调试命令

# 网络策略
adb shell dumpsys network_policy

# 流量统计
adb shell dumpsys netstats

# VPN状态
adb shell dumpsys connectivity | grep -A 20 "VPN"

# eBPF统计
adb shell dumpsys connectivity trafficcontroller

# 设置Data Saver
adb shell cmd netpolicy set restrict-background true

# 查看UID策略
adb shell cmd netpolicy list uid-policy

6.3 官方文档


后记

网络策略和VPN是Android网络子系统中最复杂的两个模块,它们共同保障了:

  • 用户隐私:VPN加密保护数据传输
  • 流量控制:防止后台应用偷跑流量
  • 电量优化:限制不必要的网络活动
  • 安全隔离:企业级网络访问控制

系列文章


欢迎来我中的个人主页找到更多有用的知识和有趣的产品