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

Android 13下全网络设备IP与MAC扫描的技术求助

网络设备IP与MAC扫描问题及解决方案

问题描述

我正在开发一款扫描网络中所有设备IP与MAC的应用,但目前仅能获取手机类设备的IP,无法扫描到通过网线连接路由器的电脑或笔记本。尝试使用arp -a命令实现,但该方法在Android 13上已失效。观察到Network Scanner和Wi-Fi Monitor等应用可正常获取IP与MAC,附上当前实现代码,寻求正确实现全设备IP与MAC扫描的方案:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private static final int REQUEST_CODE_PERMISSIONS = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED ||
                ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.INTERNET,
                    Manifest.permission.ACCESS_WIFI_STATE
            }, REQUEST_CODE_PERMISSIONS);
        }

        Button scanButton = findViewById(R.id.scanButton);
        scanButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new ScanNetworkTask().execute();
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                findViewById(R.id.scanButton).setEnabled(true);
            } else {
                Log.e(TAG, "Permissions denied by user.");
                findViewById(R.id.scanButton).setEnabled(false);
            }
        }
    }

    private class ScanNetworkTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                scanNetwork();
            } catch (IOException e) {
                Log.e(TAG, "IOException in scanNetwork: ", e);
            }
            return null;
        }

        private void scanNetwork() throws IOException {
            try {
                NetworkInterface wlan0 = NetworkInterface.getByName("wlan0");
                if (wlan0 != null && wlan0.isUp()) {
                    Enumeration<InetAddress> addresses = wlan0.getInetAddresses();
                    while (addresses.hasMoreElements()) {
                        InetAddress address = addresses.nextElement();
                        if (address.isLoopbackAddress() || !address.isSiteLocalAddress()) {
                            continue;
                        }

                        String subnet = getSubnet(address.getHostAddress());
                        Log.d(TAG, "Scanning subnet: " + subnet);

                        for (int i = 1; i < 255; i++) {
                            String host = subnet + i;
                            try {
                                InetAddress inetAddress = InetAddress.getByName(host);
                                if (inetAddress.isReachable(1000)) {
                                    Log.d(TAG, "Host: " + inetAddress.getHostAddress() + " is reachable");
                                    Log.d(TAG, "Hostname: " + inetAddress.getHostName());
                                }
                            } catch (IOException e) {
                                Log.e(TAG, "IOException in scanNetwork: ", e);
                            }
                        }
                    }
                }
            } catch (SocketException e) {
                Log.e(TAG, "SocketException in scanNetwork: ", e);
            }
        }

        private String getSubnet(String ipAddress) {
            return ipAddress.substring(0, ipAddress.lastIndexOf('.') + 1);
        }
    }
}

解决方案

1. 补充必要权限

Android 13+对网络扫描权限有严格限制,需要添加并动态申请以下权限:

  • ACCESS_FINE_LOCATION:Wi-Fi扫描必须的位置权限
  • CHANGE_WIFI_STATE:触发Wi-Fi扫描以更新ARP缓存
  • ACCESS_NETWORK_STATE:获取当前网络状态
  • READ_PHONE_STATE:部分设备需要此权限读取网络信息

在AndroidManifest.xml中声明:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

动态申请代码修改:

ActivityCompat.requestPermissions(this, new String[]{
        Manifest.permission.INTERNET,
        Manifest.permission.ACCESS_WIFI_STATE,
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.CHANGE_WIFI_STATE,
        Manifest.permission.ACCESS_NETWORK_STATE,
        Manifest.permission.READ_PHONE_STATE
}, REQUEST_CODE_PERMISSIONS);

2. 替代arp -a的ARP缓存读取方案

Android 13禁止普通应用执行arp -a,但可以直接读取系统ARP缓存文件/proc/net/arp,该文件包含所有已发现设备的IP、MAC等信息。

添加读取ARP缓存的方法:

private Map<String, String> readArpCache() throws IOException {
    Map<String, String> ipMacMap = new HashMap<>();
    BufferedReader reader = new BufferedReader(new FileReader("/proc/net/arp"));
    String line;
    // 跳过表头行
    reader.readLine();
    while ((line = reader.readLine()) != null) {
        String[] parts = line.split("\\s+");
        if (parts.length >= 4) {
            String ip = parts[0];
            String mac = parts[3];
            // 过滤无效MAC(全0或未分配)
            if (!mac.equals("00:00:00:00:00:00")) {
                ipMacMap.put(ip, mac);
            }
        }
    }
    reader.close();
    return ipMacMap;
}

3. 优化设备可达性检测

inetAddress.isReachable()依赖ICMP协议,很多有线设备会禁用ping响应,导致无法被检测到。可以结合多端口TCP/UDP探测提高检测率:

private boolean isHostReachable(String ip) {
    // 尝试扫描常用端口:80(HTTP), 443(HTTPS), 22(SSH), 3389(RDP)
    int[] ports = {80, 443, 22, 3389};
    for (int port : ports) {
        try (Socket socket = new Socket()) {
            socket.connect(new InetSocketAddress(ip, port), 500);
            return true;
        } catch (IOException ignored) {
            // 端口不通,尝试下一个
        }
    }
    // 最后尝试ICMP
    try {
        return InetAddress.getByName(ip).isReachable(1000);
    } catch (IOException e) {
        return false;
    }
}

4. 动态获取Wi-Fi网络接口

不要硬编码wlan0,不同设备的Wi-Fi接口名称可能不同(如wlan1、eth0),通过遍历网络接口获取正确的Wi-Fi接口:

private NetworkInterface getWifiInterface() throws SocketException {
    Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
    while (interfaces.hasMoreElements()) {
        NetworkInterface ni = interfaces.nextElement();
        // 判断是否为Wi-Fi接口:名称包含wlan,且不是回环接口
        if (ni.isUp() && !ni.isLoopback() && ni.getName().contains("wlan")) {
            return ni;
        }
    }
    return null;
}

整合后的扫描逻辑

修改scanNetwork方法,结合以上优化点:

private void scanNetwork() throws IOException {
    NetworkInterface wifiInterface = getWifiInterface();
    if (wifiInterface == null) {
        Log.e(TAG, "Wi-Fi interface not found");
        return;
    }

    Enumeration<InetAddress> addresses = wifiInterface.getInetAddresses();
    while (addresses.hasMoreElements()) {
        InetAddress address = addresses.nextElement();
        if (address.isLoopbackAddress() || !address.isSiteLocalAddress()) {
            continue;
        }

        String subnet = getSubnet(address.getHostAddress());
        Log.d(TAG, "Scanning subnet: " + subnet);

        // 并行扫描提高效率(注意线程控制)
        ExecutorService executor = Executors.newFixedThreadPool(10);
        for (int i = 1; i < 255; i++) {
            final String host = subnet + i;
            executor.submit(() -> {
                if (isHostReachable(host)) {
                    Log.d(TAG, "Host reachable: " + host);
                }
            });
        }
        executor.shutdown();
        try {
            executor.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        // 读取ARP缓存获取MAC地址
        Map<String, String> ipMacMap = readArpCache();
        for (Map.Entry<String, String> entry : ipMacMap.entrySet()) {
            Log.d(TAG, "IP: " + entry.getKey() + ", MAC: " + entry.getValue());
        }
    }
}

注意事项

  • 部分国产ROM可能对/proc/net/arp的读取有限制,可以尝试使用ConnectivityManager的getAllNetworks()接口辅助获取网络设备信息。
  • 并行扫描时要控制线程数量,避免触发系统的网络限流。
  • 扫描前可以调用WifiManager.startScan()触发Wi-Fi扫描,更新ARP缓存,提高设备发现率。

内容的提问来源于stack exchange,提问作者Luis Carlos Bautista Machuca

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 08:42:04