Android11--有线网络源码分析

1. 有线网络配置

1.1 配置流程

  1. APK获取用户配置的网络参数(IP地址、是否开启DHCP、子网掩码、默认网关、DNS)。
  2. 调用方法函数将字符串形式的网络参数转化为StaticIpConfiguration(定义静态IP配置的详细信息),然后转化为IpConfiguration(定义IP配置方式)。
  3. 调用EthernetManager的setConfiguration进行网络配置,该请求会被发送到系统服务层。
  4. EthernetServiceImpl处理远程网络配置请求。
  5. EthernetNetworkFactory管理类处理进一步处理链接请求。

1.2 源码分析

1.2.1 APK配置网络参数

APK中写一个setEthernetStaticInfo()接口,用来传递要配置的有线网络参数,包括网路接口名称(区分多网络网络)、IP地址、子网掩码、默认网关、DNS。

  1. 首先调用getIPv4Address()和maskStr2InetMask()方法把字符串形式的网络参数转换为StaticIpConfiguration中参数类型一致。

  2. 新建并初始化StaticIpConfiguration对象。

  3. 创建IpConfiguration,指定IP配置方式,并把包含网络参数的StaticIpConfiguration对象传入。

IpConfiguration.IpAssignment如果设置为静态IP必须传入网络参数StaticIpConfiguration。

  1. 通过上层配置IP的管理类EthernetManager配置有线网络。
<MainActivity.java>

    /*
     * 设置静态网络参数
     */
    public void setEthernetStaticInfo(String iface, String ipAddress, String subnetMask, String gateway, String dns1, String dns2) {

        EthernetManager ethernetManager = (EthernetManager) getSystemService(Context.ETHERNET_SERVICE);
        if (ethernetManager == null){
            Log.e(TAG, "setEthernetStaticInfo ethernetManager == null !");
            return;
        }

        try {
            Log.d(TAG, "ipAddress: " + ipAddress + "subnetMask: " + subnetMask + "gateway: " + gateway + "dns1: " + dns1 + "dns2: " + dns2);

            // 修改网络参数
            Inet4Address inetAddr = getIPv4Address(ipAddress);
            int prefixLength = maskStr2InetMask(subnetMask);
            InetAddress gatewayAddr = getIPv4Address(gateway);
            InetAddress dnsAddr = getIPv4Address(dns1);
            InetAddress dns2Addr = getIPv4Address(dns1);

            if (TextUtils.isEmpty(ipAddress) || prefixLength == 0 || TextUtils.isEmpty(gateway) || TextUtils.isEmpty(dns1) || TextUtils.isEmpty(dns2)) {
                Log.e(TAG, "setEthernetStaticInfo ip or mask or gateway or dns1 or dns2 is wrong!");
                return;
            }

            StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
            staticIpConfiguration.ipAddress = new LinkAddress(inetAddr, prefixLength); // IP地址、子网掩码
            staticIpConfiguration.gateway = gatewayAddr; // 网关
            staticIpConfiguration.dnsServers.add(dnsAddr); // 首选DNS
            staticIpConfiguration.dnsServers.add(dns2Addr); // 备选DNS

            IpConfiguration ipConfiguration = new IpConfiguration(IpConfiguration.IpAssignment.STATIC, IpConfiguration.ProxySettings.NONE, staticIpConfiguration, null);
            ethernetManager.setConfiguration(iface, ipConfiguration);

        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "setStaticIpConfiguration error :" + e);
        }

    }

以下两个接口方法是Android11源码中提供的。

<MainActivity.java>

    private Inet4Address getIPv4Address(String text) {
        try {
            return (Inet4Address) NetworkUtils.numericToInetAddress(text);
        } catch (IllegalArgumentException | ClassCastException e) {
            return null;
        }
    }

    /*
     * convert subMask string to prefix length
     */
    private int maskStr2InetMask(String maskStr) {
        StringBuffer sb;
        String str;
        int inetmask = 0;
        int count = 0;
        /*
         * check the subMask format
         */
        Pattern pattern = Pattern.compile("(^((\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])$)|^(\\d|[1-2]\\d|3[0-2])$");
        if (pattern.matcher(maskStr).matches() == false) {
            Log.e(TAG, "maskStr2InetMask subMask is error!");
            return 0;
        }

        String[] ipSegment = maskStr.split("\\.");
        for (int n = 0; n < ipSegment.length; n++) {
            sb = new StringBuffer(Integer.toBinaryString(Integer.parseInt(ipSegment[n])));
            str = sb.reverse().toString();
            count = 0;
            for (int i = 0; i < str.length(); i++) {
                i = str.indexOf("1", i);
                if (i == -1)
                    break;
                count++;
            }
            inetmask += count;
        }
        return inetmask;
    }

网络参数配置成功可以通过监听网络状态改变的广播来更新UI显示。

1.2.2 EthernetManager.setConfiguration()

  1. 上层配置IP的管理类EthernetManager的setConfiguration()会将配置网络的请求通过Binder发送到系统服务层。
<frameworks/base/core/java/android/net/EthernetManager.java>

    /**
     * Set Ethernet configuration.
     * @hide
     */
    @UnsupportedAppUsage
    public void setConfiguration(String iface, IpConfiguration config) {
        try {
            mService.setConfiguration(iface, config);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
  1. EthernetServiceImpl处理远程请求。

EthernetService在SystemServer检测到设备支持以太网时就启动了。

EthernetService的构造函数中会创建EthernetServiceImpl实例,EthernetServiceImpl中的setConfiguration会通知EthernetTracker更新网络配置。

<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetServiceImpl.java>

    /**
     * Set Ethernet configuration
     */
    @Override
    public void setConfiguration(String iface, IpConfiguration config) {
        if (!mStarted.get()) {
            Log.w(TAG, "System isn't ready enough to change ethernet configuration");
        }

        NetworkStack.checkNetworkStackPermission(mContext);

        if (mTracker.isRestrictedInterface(iface)) {
            enforceUseRestrictedNetworksPermission();
        }

        // TODO: this does not check proxy settings, gateways, etc.
        // Fix this by making IpConfiguration a complete representation of static configuration.
        mTracker.updateIpConfiguration(iface, new IpConfiguration(config));
    }
  1. EthernetTracker的updateIpConfiguration()方法将配置内容保存到配置文件中,然后通过Handler异步更新指定网络接口的IP配置。

mConfigStore.write()方法将网络参数写入EthernetConfigStore中的常量ipconfigFile文件中“/misc/ethernet/ipconfig.txt”

<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetTracker.java>

    void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
        if (DBG) {
            Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration);
        }

        mConfigStore.write(iface, ipConfiguration);
        mIpConfigurations.put(iface, ipConfiguration);

        mHandler.post(() -> mFactory.updateIpConfiguration(iface, ipConfiguration));
    }
  1. EthernetNetworkFactory的updateIpConfiguration会调用内部类NetworkInterfaceState的setIpConfig()方法。
<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetNetworkFactory.java>

    void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
        NetworkInterfaceState network = mTrackingInterfaces.get(iface);
        if (network != null) {
            network.setIpConfig(ipConfiguration);
        }
    }
  1. NetworkInterfaceState的setIpConfig()方法将IpConfiguration内容保存到私有变量mIpConfig中然后重启当前网络。

在start()中,保存的mIpConfig会传入到provisionIpClient()中用作初始化配置网络接口参数。

<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetNetworkFactory.NetworkInterfaceState>

        void setIpConfig(IpConfiguration ipConfig) {
            if (Objects.equals(this.mIpConfig, ipConfig)) {
                if (DBG) Log.d(TAG, "ipConfig have not changed,so ignore setIpConfig");
                return;
            }
            this.mIpConfig = ipConfig;
            if (mNetworkAgent != null) {
                restart();
            }
        }

        void restart(){
            if (DBG) Log.d(TAG, "reconnecting Etherent");
            stop();
            start();
        }

        private void start() {
            if (mIpClient != null) {
                if (DBG) Log.d(TAG, "IpClient already started");
                return;
            }
            if (DBG) {
                Log.d(TAG, String.format("Starting Ethernet IpClient(%s)", name));
            }

            mIpClientCallback = new IpClientCallbacksImpl();
            IpClientUtil.makeIpClient(mContext, name, mIpClientCallback);
            mIpClientCallback.awaitIpClientStart();
            if (sTcpBufferSizes == null) {
                sTcpBufferSizes = mContext.getResources().getString(
                        com.android.internal.R.string.config_ethernet_tcp_buffers);
            }
            provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
        }

2. 有线网络参数获取

2.1 获取方法

  1. 通过Android提供的系统服务接口ConnectivityManager和网络管理接口类LinkProperties获取以太网连接信息。

  2. 通过Java提供的网络接口类NetworkInterface 获取当前网络的详细信息,判断是否为以太网连接。

  3. 通过Android提供的以太网管理类EthernetManager获取有线网络的详细信息。

2.2 流程分析

2.2.1 通过ConnectivityManager和LinkProperties获取

ConnectivityManager是Android提供的一个系统服务接口,用于管理网络连接,而其中的getLinkProperties()方法是一个Android API方法,用于获取指定网络链路属性。

1. APK获取网络参数

(1)首先获取 ConnectivityManager 的实例。

(2)通过getAllNetworks获取设备支持的所有网络类型(以太网、无线、移动数据)的链接状态信息。

(3)遍历获取已连接网络的链路属性,通过linkPropertie中的方法获取网络信息。

(4)这里要获取有线网络的信息,所以筛选出以太网,通过getLinkAddresses()获取IP地址和子网掩码、通过getRoutes()获取网关、通过getDnsServers()获取DNS。

(5)for循环会遍历到所有的信息,可以通过instanceof筛选IPV4网络或者IPV6网络。

<MainActivity.java>

    public static void getEthernetNetworkInfo1(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (cm != null) {
            Log.d(TAG, "network size = " + cm.getAllNetworks().length);
            for (Network network : cm.getAllNetworks()) {

                LinkProperties linkProperties = cm.getLinkProperties(network);
                if (linkProperties != null) {
                    String interfaceName = linkProperties.getInterfaceName();
                    if (interfaceName != null && interfaceName.startsWith("eth")) {
                        Log.d(TAG, "all Interface: " + interfaceName);
                        for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) {
                            Log.d(TAG, "all IP Address: " + linkAddress.getAddress().getHostAddress());
                            Log.d(TAG, "all Subnet Mask: " + linkAddress.getNetworkPrefixLength());
                        }
                        for (RouteInfo route : linkProperties.getRoutes()) {
                            if (route.getDestination().getAddress().isAnyLocalAddress()) {
                                Log.d(TAG, "all Default Gateway: " + route.getGateway().getHostAddress());
                            }
                        }
                        for (InetAddress dns : linkProperties.getDnsServers()) {
                            Log.d(TAG, "all DNS Server: " + dns.getHostAddress());
                        }
                    }
                }
            }
        }
    }

2. LinkProperties底层调用

(1)ConnectivityManager向系统服务发送请求获取连接网络的链路属性linkProperties。

<frameworks/base/core/java/android/net/ConnectivityManager.java>

    public LinkProperties getLinkProperties(int networkType) {
        try {
            return mService.getLinkPropertiesForType(networkType);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

(2)ConnectivityService接收到请求后从NetworkAgentInfo中查询指定网络链路属性,NetworkAgentInfo中的网络链路属性是在网络连接时赋值的,这里不做分析。

<frameworks/base/services/core/java/com/android/server/ethernet/ConnectivityService.java>

    public LinkProperties getLinkPropertiesForType(int networkType) {
        enforceAccessPermission();
        NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
        final LinkProperties lp = getLinkProperties(nai);
        if (lp == null) return null;
        return linkPropertiesRestrictedForCallerPermissions(
                lp, Binder.getCallingPid(), Binder.getCallingUid());
    }

2.2.2 通过NetworkInterface获取

NetworkInterface是Java提供的一个java.net 包,用于获取和操作网络接口的详细信息。

1. APK获取网络参数。

(1)获取NetworkInterface实例,筛选出以太网络,然后使用NetworkInterface中的getInetAddresses()获取IP地址、用getInterfaceAddresses获取子网掩码。

(2)NetworkInterface 未提供获取网关和DNS的功能,需要通过其他接口获取。

<MainActivity.java>

    public static void getEthernetNetworkInfo() {
        try {
            // 获取所有网络接口
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface networkInterface = interfaces.nextElement();

                // 确定以太网接口(通常是 eth0 或者类似的名称)
                if (networkInterface.getName().equalsIgnoreCase("eth0")) {
                    // 获取接口的所有 IP 地址
                    Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
                    while (inetAddresses.hasMoreElements()) {
                        InetAddress inetAddress = inetAddresses.nextElement();
                        if (!inetAddress.isLoopbackAddress()) {
                            // 输出有效的 IP 地址
                            Log.d("Network", "IP Address: " + inetAddress.getHostAddress());
                        }
                    }

                    // 获取子网掩码
                    NetworkInterface temp = NetworkInterface.getByName("eth0");
                    if (temp != null) {
                        List<InterfaceAddress> interfaceAddresses = temp.getInterfaceAddresses();
                        for (InterfaceAddress interfaceAddress : interfaceAddresses) {
                            // 输出子网掩码
                            Log.d("Network", "Subnet Mask: " + interfaceAddress.getNetworkPrefixLength());
                        }
                    }
                    // 获取默认网关
                    List<InetAddress> gateways = getDefaultGateways();
                    for (InetAddress gateway : gateways) {
                        Log.d("Network", "Default Gateway: " + gateway.getHostAddress());
                    }

                    // 获取 DNS
                    List<InetAddress> dnsAddresses = getDnsAddresses();
                    for (InetAddress dns : dnsAddresses) {
                        Log.d("Network", "DNS: " + dns.getHostAddress());
                    }
                }
            }
        } catch (Exception e) {
            Log.e("Network", "Error getting network information", e);
        }
    }

    // 获取默认网关的方法
    private static List<InetAddress> getDefaultGateways() {
        List<InetAddress> gateways = new ArrayList<>();
        try {
            // 获取默认网关地址
            NetworkInterface networkInterface = NetworkInterface.getByName("eth0");
            if (networkInterface != null) {
                for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
                    InetAddress gateway = address.getAddress();
                    gateways.add(gateway);
                }
            }
        } catch (SocketException e) {
            Log.e("Network", "Error getting gateways", e);
        }
        return gateways;
    }

    // 获取 DNS 服务器地址的方法
    private static List<InetAddress> getDnsAddresses() {
        List<InetAddress> dnsAddresses = new ArrayList<>();
        try {
            // 获取 DNS 信息,Android 提供的系统设置会包含 DNS 服务器地址
            InetAddress dns = InetAddress.getByName("8.8.8.8"); // 使用 Google 公共 DNS 作为示例
            dnsAddresses.add(dns);
        } catch (UnknownHostException e) {
            Log.e("Network", "Error getting DNS addresses", e);
        }
        return dnsAddresses;
    }

2. NetworkInterface底层调用

(1)Java层通过调用networkInterface.getInetAddresses()获取网络IP地址,该方法会调用到JNI中的接口,然后调用到底层C/C++代码。

(2)JNI调用会进一步调用系统接口管理函数,如getifaddrs,用于获取网络接口信息。

(3)系统调用getifaddrs从内核中获取网络接口的配置信息。

(4)获取到的网络接口信息通过JNI返回到Java层,最终返回给调用者。

2.2.3 通过EthernetManager获取

EthernetManager时Android提供的隐藏系统API,用于管理设备以太网连接,通常不直接对普通开发者开放,其中的功能接口包括 启用和禁用以太网接口、获取连接状态、配置网络等。

1. APK获取网络参数。

(1)获取静态网络参数可以通过StaticIpConfiguration中获取

首先从上层网络管理类EthernetManager获取到指定网络的IpConfiguration,然后从IpConfiguration中获取静态网络配置类StaticIpConfiguration。

然后从StaticIpConfiguration获取需要的网络信息即可。

<MainActivity.java>

    private void getStaticEthernetInfo(Context context, String iface) {

        EthernetManager ethernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
        if (ethernetManager == null) {
            Log.e(TAG, "getStaticEthernetInfo ethernetManager == null !");
            return;
        }

        IpConfiguration ipConfiguration = ethernetManager.getConfiguration(iface);
        if (ipConfiguration == null) {
            Log.e(TAG, "getStaticEthernetInfo ipConfiguration == null !");
            return;
        }

        StaticIpConfiguration staticIpConfiguration = ipConfiguration.getStaticIpConfiguration();
        if (staticIpConfiguration != null && ipConfiguration.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {

            // 获取IP地址
            LinkAddress ipAddress = staticIpConfiguration.ipAddress;
            try {
                Log.d(TAG, iface + ", IP Address: " + ipAddress.getAddress().getHostAddress());
                Log.d(TAG, iface + ", Subnet Mask: " + intToIPv4Mask(ipAddress.getNetworkPrefixLength()));

                // 获取默认网关
                InetAddress gateway = staticIpConfiguration.gateway;
                Log.d(TAG, iface + ", Default Gateway: " + gateway.getHostAddress());

                // 获取DNS服务器
                for (InetAddress dns : staticIpConfiguration.dnsServers) {
                    Log.d(TAG, iface + ", DNS Server: " + dns.getHostAddress());
                }
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "getStaticEthernetInfo error :" + e);
            }
        }
    }

    /*
     * 子网掩码长度转IP
     */
    private static String intToIPv4Mask(int prefixLength) {
        long mask = ~((1L << (32 - prefixLength)) - 1);
        return ((mask >> 24) & 0xFF) + "." +
                ((mask >> 16) & 0xFF) + "." +
                ((mask >> 8) & 0xFF) + "." +
                (mask & 0xFF);
    }

(2)获取动态DHCP网络参数可以直接从EthernetManager中获取

网络状态需要通过ipConfiguration.getIpAssignment()获取,其他参数直接从EthernetManager中获取即可。

<MainActivity.java>

    private void getDhcpEthernetInfo(Context context, String iface){

        EthernetManager ethernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
        if (ethernetManager == null) {
            Log.e(TAG, "getDhcpEthernetInfo ethernetManager == null !");
            return;
        }
        IpConfiguration ipConfiguration = ethernetManager.getConfiguration(iface);
        if (ipConfiguration != null) {
            Log.d(TAG, iface + ",getDhcp: " + (ipConfiguration.getIpAssignment() == IpConfiguration.IpAssignment.DHCP));
        }
        // BSP可以直接从EthernetManager中获取有线网络信息
        try {
            Log.d(TAG, iface + ",getIpAddress: " + ethernetManager.getIpAddress(iface));
            Log.d(TAG, iface + ",getNetmask: " + ethernetManager.getNetmask(iface));
            Log.d(TAG, iface + ",getGateway: " + ethernetManager.getGateway(iface));
            Log.d(TAG, iface + ",getDns: " + ethernetManager.getDns(iface));
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "getDhcpEthernetInfo error :" + e);
        }
    }

2. EthernetManager源码分析

(1)以EthernetManager.getIpAddress()为例,调用该方法后EthernetManager会向系统服务层发送获取IP地址的请求。

<frameworks/base/core/java/android/net/EthernetManager.java>

    public String getIpAddress(String iface) {
        try {
            return mService.getIpAddress(iface);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

(2)服务层接收到请求后会在EthernetServiceImpl的getConfiguration()中处理请求。

<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetServiceImpl.java>

    public IpConfiguration getConfiguration(String iface) {
        enforceAccessPermission();

        if (mTracker.isRestrictedInterface(iface)) {
            enforceUseRestrictedNetworksPermission();
        }

        return new IpConfiguration(mTracker.getIpConfiguration(iface));
    }

(3)然后调用EthernetTracker中的getIpConfiguration()返回指定网络的IP地址。

其中mIpConfigurations是一个HashMap,在启动网络的start()中就已经通过EthernetConfigStore将配置文件中的参数读出到mIpConfigurations中了,所以get的时候可以直接将结果返回。

<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetTracker.java>

    IpConfiguration getIpConfiguration(String iface) {
        return mIpConfigurations.get(iface);
    }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容