Como conseguir um Mac-Address de um IP de rede local usando .NET Core em Linux?

Há um tempo abri um tema em forma de pergunta no Stackoverflow, tanto em português quanto em inglês.

Em uma rede local, queria ter controle de dispositivos que se conectassem a um servidor para realizar tarefas automáticas. O servidor local poderia enviar e receber comandos desses dispositivos. Além do essencial, queria gravar um histórico, e saber quem era quem, pelo IP. Como os dispositivos tinham muitas limitações (não tinham processadores para instalar ferramentas, apenas placas de WiFi), o único jeito era capturar a comunicação TCP/IP destes dispositivos com o servidor. A exclusividade teórica do MAC-Address de uma placa WiFi poderia ser o identificador exclusivo de cada dispositivo. Mas como conseguir isso em uma plataforma mais universal?

Graças ao Alessandro Schneider, pude desenvolver uma solução bem simples. Ele ainda recomenda o uso para Linux do arping, solução mais robusta, competente.

A seguir disponho uma solução para fazer um sistema que rode em W10 e Linux. Esta solução foi testada em um Raspberry 3.0.

using System.Diagnostics;
namespace Ferramentas
{
    public class Rede
    {
        ///FOR LINUX
        public static string Bash(string cmd)
        {
            var escapedArgs = cmd.Replace("\"", "\\\"");
            var process = new Process()
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "/bin/bash",
                    Arguments = $"-c \"{escapedArgs}\"",
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                }
            };
            process.Start();
            string result = process.StandardOutput.ReadToEnd();
            process.WaitForExit();
            return result;
        }
        ///FOR WINDOWS
        public static string CMD(string cmd)
        {
            var process = new Process()
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "cmd.exe",
                    Arguments = $@"/c {cmd}",
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                }
            };
            process.Start();
            string result = process.StandardOutput.ReadToEnd();
            process.WaitForExit();
            return result;
        }
    //Mais métodos
    }
}

Poderia transformar esses dois métodos em um só? Sim!!! Mas estão aqui, separados, apenas para mostrar que muitas coisas em Windows e Linux são comuns com o .NET Core.

Continuando…

using System.Diagnostics;
using System.Net.NetworkInformation;
 
namespace Ferramentas
{
    public class Rede
    {
        //Método Bash
        //Método CMD
 
        //Tratativa de erro simplificada, sem Log, sem envio por e-mail e sem _erro.Clear()
        private static StringBuilder _erro = new StringBuilder();
        public static string ErrorMessage { get { return _erro.ToString(); } }
 
        ///Primeira tentativa de comunicação
        public static bool Ping(string ipOrName, int timeout = 0, bool throwExceptionOnError = false)
        {
            bool p = false;
            try
            {
                using (Ping ping = new Ping())
                {
                    PingReply reply = null;
                    if (timeout > 0)
                        reply = ping.Send(ipOrName, timeout);
                    else
                        reply = ping.Send(ipOrName);
                    if (reply != null)
                        p = (reply.Status == IPStatus.Success);
                    //p = Convert.ToInt32(reply.RoundtripTime);
                }
            }
            catch (PingException e)
            {
                _erro.Append(e.Message);
                _erro.Append(Environment.NewLine);
                if (throwExceptionOnError) throw e;
            }
            catch (Exception ex)
            {
                _erro.Append(ex.Message);
                _erro.Append(Environment.NewLine);
            }
            return p;
        }
    //Mais métodos
    }
}

Agora usaremos duas classes (poderia ser uma) para chamar as ferramentas. Essas classes é que poderiam ser as públicas, para uso externo.

using System.Diagnostics;
using System.Net.NetworkInformation;
using System.Text;
using System.Text.RegularExpressions;
 
namespace Ferramentas
{
    public class Rede
    {
        //Método Bash
        //Método CMD
        //Método Ping
 
        public static string TryGetMacAddressOnLinux(string ip)
        {
            _erro.Clear();
            //Descobrir o IP para usar o comando arp (comum tanto em Windows quanto em Linux)
            if (!Ping(ip))
                _erro.Append("Não foi possível testar a conectividade (ping) com o ip informado.\n");
 
            //O comando arp devolve o MAC-Address do IP informado
            string arp = $"arp -a {ip}";
            string retorno = Bash(arp);
            StringBuilder sb = new StringBuilder();
 
            //A diferença para o Windows é o formato do retorno. Linux usa ":" como separador 
            string pattern = @"(([a-f0-9]{2}:?){6})";
            int i = 0;
            foreach (Match m in Regex.Matches(retorno, pattern, RegexOptions.IgnoreCase))
            {
                if (i > 0)
                    //Contrariando Windows e Linux, devolvo o resultado com o separador ";". 
                    sb.Append(";");
                sb.Append(m.ToString());
                i++;
            }
            return sb.ToString();
        }
 
        public static string TryGetMacAddressOnWindows(string ip)
        {
            _erro.Clear();
            //Descobrir o IP para usar o comando arp (comum tanto em Windows quanto em Linux)
            if (!Ping(ip))
                _erro.Append("Não foi possível testar a conectividade (ping) com o ip informado.\n");
 
            //O comando arp devolve o MAC-Address do IP informado
            string arp = $"arp -a {ip}";
            string retorno = CMD(arp);
            StringBuilder sb = new StringBuilder();
 
            //A diferença para o Linux é o formato do retorno. Windows usa "-" como separador 
            string pattern = @"(([a-f0-9]{2}-?){6})";
            int i = 0;
            foreach (Match m in Regex.Matches(retorno, pattern, RegexOptions.IgnoreCase))
            {
                if (i > 0)
                    //Contrariando Windows e Linux, devolvo o resultado com o separador ";". 
                    sb.Append(";");
                sb.Append(m.ToString());
                i++;
            }
            return sb.ToString();
        }
 
 
    //Mais métodos
    }
}

Agora, para usar, pode-se usar alguma lógica que ache mais conveniente.
Em essência seria algo como:

public Socket MeuSocket { get; set; }
 
IPEndPoint remoteIpEndPoint = MeuSocket.RemoteEndPoint as IPEndPoint;
if (remoteIpEndPoint == null)
{
    //Trata erro e sai
}
 
if (_tipo == MeuEnum.LINUX_ARM || _tipo == MeuEnum.LINUX)
{
    macAddress = Rede.TryGetMacAddressOnLinux(remoteIpEndPoint.Address.ToString());
}
else
{
    macAddress = Rede.TryGetMacAddressOnWindows(remoteIpEndPoint.Address.ToString());
}

Espero que ajude alguém!!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *