4
\$\begingroup\$

well, as said in the title: We need to get a mac-address of a NIC that is providing the connection to a particular host. We use the mac-address during the login process using MSSQL and Entity Framework.

technology: C#.NET 4.0, EF 4.1

private string _max_address;
public string ConnectionString { get; };

public string MacAddress {
    get {
        if (this._mac_address == null) {
            SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder(this.ConnectionString);

            IPAddress[] addres = Dns.GetHostAddresses(csb.DataSource.Split('\\')[0]);
            //find nic interface address from routing table

            Process p = new Process();
            // Redirect the output stream of the child process.
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.StartInfo.CreateNoWindow = false;
            p.StartInfo.FileName = "route";
            p.StartInfo.Arguments = "PRINT";
            p.Start();
            string output = p.StandardOutput.ReadToEnd();
            p.WaitForExit();
            p.Close();
            p.Dispose();


            string[] lines = output.Split(new string[]{"\r\n"}, StringSplitOptions.RemoveEmptyEntries);

            List<Route> routes = new List<Route>();
            bool found = false;
            foreach (string line in lines) {
                if (line == "Active Routes:") {
                    if (found) //if routes have already been found exit loop, meaning this is IPv6 routes, should never reach this point
                        break;
                    found = true;
                    continue; //next line
                }

                if (found) {
                    if (line[0] == 'N')  //if the line is headers skip it
                        continue;
                    else if (line[0] == '=')  //if the routes have ended exit loop
                        break;
                    routes.Add(new Route(line)); //read route, custom class
                }
            }
            routes.Sort(); //sort routes by Mask, then by Address
            string nic_addr = "";

            foreach (IPAddress addr in addres) {
                if (addr.AddressFamily == AddressFamily.InterNetwork) { //if IPv4
                    for (int i = routes.Count - 1; i >= 0; i--) { //start from the largest Mask
                        if(routes[i].IncludesHost(addr)) { //whether the ip fits in the subnet
                            nic_addr = routes[i].ReadableInterface;
                            break; //if so, save interface name, exit loop
                        }
                    }
                    break;
                }
            }

            //Get NIC of found Interface by IP
            foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces()) {
                if (nic.OperationalStatus == OperationalStatus.Up) {
                    IPInterfaceProperties properties = nic.GetIPProperties();
                    foreach (UnicastIPAddressInformation addr_info in properties.UnicastAddresses) {
                        if (addr_info.Address.AddressFamily == AddressFamily.InterNetwork && addr_info.Address.ToString()==nic_addr) {
                            this._mac_address = nic.GetPhysicalAddress().ToString();
                            break;  //if address matches that of the routing table interface address then save MAC and exit loop
                        }
                    }
                    if (this._mac_address != null)
                        break; //if mac address is found exit loop;
                }
            }
        }
        return this._mac_address;
    }
}

public class Route : IComparable
{
    private uint _address;
    private uint _mask;
    private uint _interface;
    private int _metric;

    public uint Address { get { return this._address; } }
    public uint Mask { get { return this._mask; } }
    public uint Interface { get { return this._interface; } }

    public string ReadableAddress { get { return Route.IPLongToString(this._address); } }
    public string ReadableMask { get { return Route.IPLongToString(this._mask); } }
    public string ReadableInterface { get { return Route.IPLongToString(this._interface); } }

    public Route(string[] route)
    {
        this._address=IPStringToLong(route[0]);
        this._mask=IPStringToLong(route[1]);
        this._interface=IPStringToLong(route[3]);
        this._metric=UInt16.Parse(route[4]);
    }

    public Route(string line)
        : this(line.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries))
    {
    }

    public static uint IPStringToLong(string str)
    {
        string[] values = str.Split(new char[]{'.'});
        uint multiplier = 1;
        uint value = 0;
        for(int i=3;i>=0;i--) {
            value+=UInt32.Parse(values[i])*multiplier;
            multiplier*=256;
        }
        return value;
    }

    public static string IPLongToString(uint addr)
    {
        byte[] bytes = BitConverter.GetBytes(addr);
        string[] bs = new string[]{"","","",""};

        for(int i=0;i<4;i++) {
             bs[i] = bytes[3-i].ToString();
        }
        return String.Join(".",bs);
    }

    public bool IncludesHost(IPAddress addr)
    {
        if (addr.AddressFamily == AddressFamily.InterNetwork)
            return this.IncludesHost(Route.IPStringToLong(addr.ToString()));
        else
            return false;
    }

    public bool IncludesHost(string addr)
    {
        return this.IncludesHost(Route.IPStringToLong(addr));
    }

    public bool IncludesHost(long addr)
    {
        return (this._mask&this._address) == (this._mask&addr);
    }

    public int CompareTo(object _y)
    {
        Route y = (Route)_y;
        if (y == null) 
            return 1;
        else
        {
            int retval = this.Mask.CompareTo(y.Mask);
            if (retval != 0) 
                return retval;
            else 
                return this.CompareToByAddress(y);
        }
    }
}

EDIT:

so far, it's a mess I don't like the solution i've come up with.

  1. get host name from connectionstring
  2. get host ip from hostname
  3. execute "route PRINT"
  4. put output in string arrray
  5. loop through output lines
  6. find "Active Routes" line
  7. skip header
  8. load route into custom Route class
  9. exit line loop
  10. loop through found routes
  11. find matching host ip&mask network&mask
  12. save its interface ip
  13. loop through NIC
  14. check whether ipv4 if so check interface ip
  15. get mac of found NIC

isn't here a more elegant way accomplishing steps 1 through 14, it's just so much code just to get a MAC-address of a particular NIC\

OR at least a better way to accomlish steps 3 through 10

EDIT: I've missed declaration of CompareToByAddress()

public int CompareToByAddress( Route y)
{
    if (y == null)
    {
        return 1;
    }
    else
    {
        int retval = this.Address.CompareTo(y.Address);
        if (retval != 0)
        {  
            return retval;
        }
        else
        {
            //should never reach this point meaning there's duplicate entry in the routing table
            return 0;
        }
    }
}

though, the solutions below do not use the CompareTo methods;

\$\endgroup\$
2
\$\begingroup\$

To expand on Strilanc's answer, you can clean up a bit further with the following:

var address = addres.First(e => e.AddressFamily.Equals(AddressFamily.InterNetwork));

// Retrieve routing table
string output = string.Empty;
using (Process p = new Process()
    {
        StartInfo = new ProcessStartInfo()
        {
            UseShellExecute = false,
            RedirectStandardOutput = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            CreateNoWindow = false,
            FileName = "route",
            Arguments = "PRINT"
        }
    })
{
    p.Start();
    output = p.StandardOutput.ReadToEnd();
    p.WaitForExit();
    p.Close();
}

// Find NIC interface from table
var nicAddr = output.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
    .SkipWhile(e => !e.Equals("Active Routes:"))
    .Skip(1)
    .TakeWhile(e => !e.Equals("Active Routes:"))
    .TakeWhile(e => !e.StartsWith("="))
    .Where(e => !e.StartsWith("N"))
    .Select(e => new Route(e))
    .OrderByDescending(e => e.Mask)
    .ThenByDescending(e => e.Address)
    .First(e => e.IncludesHost(address))
    .ReadableInterface;

// Get MAC address of associated IP address
var macAddress = NetworkInterface.GetAllNetworkInterfaces()
    .Where(e => e.OperationalStatus.Equals(OperationalStatus.Up))
    .Where(e => e.GetIPProperties().UnicastAddresses
    .Where(a => a.Address.AddressFamily.Equals(AddressFamily.InterNetwork) &&
        a.Address.ToString().Equals(nicAddr)).FirstOrDefault() != null)
        .Select(a => a.GetPhysicalAddress()).FirstOrDefault();
\$\endgroup\$
  • \$\begingroup\$ thank you, very much i'll be on this in a couple of minutes \$\endgroup\$ – ochitos Nov 3 '11 at 1:07
  • \$\begingroup\$ Worked like a charm, though I wonder, how much performance is being sacrificed on the LINQ statements. I did a small bench testing EF:LINQ against string written ADO.SQL statement, turned out about additional 200ms execution time on the EF:LINQ statement, so I just assumed EF+LINQ takes around 200ms to parse the LINQ statement to an SQL statement. \$\endgroup\$ – ochitos Nov 3 '11 at 4:14
  • \$\begingroup\$ I don't see much of a performance hit at all. The greatest burden is the 'route print' process. Aside from that, I'm only seeing a >1ms difference (average from several hundred iterations) between your long form and the LINQ posted above. \$\endgroup\$ – StyxRiver Nov 3 '11 at 12:25
  • \$\begingroup\$ A tip about LINQ: FirstOrDefault will return what you're looking for in the collection, or the default type if the collection is empty (often null). First will throw a runtime exception if there is nothing that matches the predicate. I tend to prefer using FirstOrDefault unless I'm guaranteed to have an element within the collection. \$\endgroup\$ – StyxRiver Nov 3 '11 at 21:09
0
\$\begingroup\$

I don't know about doing it more directly, but the code can definitely be cleaned up using linq as a start.

var addr = addres.First(e => e.AddressFamily == AddressFamily.InterNetwork);
var nic_addr = output.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)
                     .SkipWhile(e => e != "Active Routes:")
                     .Skip(1)
                     .TakeWhile(e => e != "Active Routes:") //if routes have already been found exit loop, meaning this is IPv6 routes, should never hit this case
                     .TakeWhile(e => e != "=") //until the routes have ended
                     .Where(e => !e.StartsWith("N")) //skip header lines
                     .Select(e => new Route(e))
                     .OrderByDescending(e => e.Mask)
                     .ThenByDescending(e => e.Address)
                     .First(e => e.IncludesHost(addr))
                     .ReadableInterface;

//Choose NIC matching the IP address
var result = NetworkInterface.GetAllNetworkInterfaces()
                             .Where(e => e.OperationalStatus == OperationalStatus.Up)
                             .SelectMany(e => e.GetIPProperties().UnicastAddresses)
                             .Where(e => e.Address.AddressFamily == AddressFamily.InterNetwork)
                             .Where(e => e.Address.ToString() == nic_addr)
                             .First();
\$\endgroup\$
  • \$\begingroup\$ wow this is awesome, how much experience do you guys have to automatically write this stuff? \$\endgroup\$ – ochitos Nov 3 '11 at 1:07
  • \$\begingroup\$ and yes I was hoping to find an already-made/"direct" solution to this problem, I'm sure there are some out there. I mean, anyone might need a mac address of the NIC making a connection to a host. I was surprised to find so little in .NET, or probably I wasn't looking in the right places. Though, I did get a hint that such solution might be found using WMI. \$\endgroup\$ – ochitos Nov 3 '11 at 7:12
  • \$\begingroup\$ Generally the linq version is easier to write than the explicit version. Instead of writing a whole for loop that sets a variable and breaks when a condition becomes true, you use 'First(condition)'. Etc. \$\endgroup\$ – Craig Gidney Nov 3 '11 at 17:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.