Bug 2033 - UnicastAddress.IPv4Mask throws NotImplementedException on Mono 2.10 running on open-embedded Linux
Summary: UnicastAddress.IPv4Mask throws NotImplementedException on Mono 2.10 running o...
Status: RESOLVED FIXED
Alias: None
Product: Class Libraries
Classification: Mono
Component: System (show other bugs)
Version: 2.10.x
Hardware: Other Linux
: --- normal
Target Milestone: Untriaged
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2011-11-15 03:16 UTC by marc
Modified: 2015-12-01 15:47 UTC (History)
5 users (show)

See Also:
Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Test case reproducing problem. (770 bytes, text/x-csharp)
2012-07-19 13:15 UTC, Tom Hindle
Details
proposed patch (10.44 KB, patch)
2012-07-19 13:23 UTC, Tom Hindle
Details | Diff

Description marc 2011-11-15 03:16:39 UTC
I am using the NetworkInterface class(es) to enumerate the NICs with their TCP/IP V4 and V6 properties. This works on .Net 4 as well as on Mono 2.10 running und open-embedded Linux (ARM) except for the UnicastAddress.IPv4Mask. When accessing the "IPv4Mask" property under Mono 2.10 a NotImplementedException gets thrown. I can query other properties of UnicastAddress without any problem but not the IPv4Mask.

Here is the code I use to query the IP properties:

var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces()
foreach (var networkIntf in networkInterfaces)
{
    var ipProperties = networkIntf.GetIPProperties();
    if (ipProperties != null)
    {
        var unicastAddress = ipProperties.UnicastAddresses.FirstOrDefault();
        return unicastAddress != null ? unicastAddress.IPv4Mask.ToString() : "";
    }
}

I don't know if this implementation is missing on other platforms as well. I found an old bug report on the Novell bugtracker that indicates this was fixed for the Windows platform:

https://bugzilla.novell.com/show_bug.cgi?id=416526

Not sure how this relates.
Comment 1 marc 2011-11-15 03:18:35 UTC
Workaround:
Fire the command "ifconfig" and capture/parse the standard output.
Comment 2 marc 2011-11-15 09:55:09 UTC
The NotImplementedException seems to be thrown for most of the properties of UnicastAddress - not only "IPv4Mask".
Comment 3 marc 2011-11-16 07:52:33 UTC
Found the following implementation in the a recent Mono tarball:


	class LinuxUnicastIPAddressInformation : UnicastIPAddressInformation
	{
		IPAddress address;

		public LinuxUnicastIPAddressInformation (IPAddress address)
		{
			this.address = address;
		}

		public override IPAddress Address {
			get { return address; }
		}

		public override bool IsDnsEligible {
			get {
				byte[] addressBytes = address.GetAddressBytes ();
				return !(addressBytes[0] == 169 && addressBytes[1] == 254);
			}
		}

		[MonoTODO("Always returns false")]
		public override bool IsTransient {
			get { return false; }
		}

		// UnicastIPAddressInformation members

		public override long AddressPreferredLifetime {
			get { throw new NotImplementedException (); }
		}

		public override long AddressValidLifetime {
			get { throw new NotImplementedException (); }
		}

		public override long DhcpLeaseLifetime {
			get { throw new NotImplementedException (); }
		}

		public override DuplicateAddressDetectionState DuplicateAddressDetectionState {
			get { throw new NotImplementedException (); }
		}

		public override IPAddress IPv4Mask {
			get { throw new NotImplementedException (); }
		}

		public override PrefixOrigin PrefixOrigin {
			get { throw new NotImplementedException (); }
		}

		public override SuffixOrigin SuffixOrigin {
			get { throw new NotImplementedException (); }
		}
	}

Comparing to the Win32 implementation (of Mono 2.10):

	class Win32UnicastIPAddressInformation : UnicastIPAddressInformation 
	{
		int if_index;
		Win32_IP_ADAPTER_UNICAST_ADDRESS info;

		public Win32UnicastIPAddressInformation (int ifIndex, Win32_IP_ADAPTER_UNICAST_ADDRESS info)
		{
			this.if_index = ifIndex;
			this.info = info;
		}

		public override IPAddress Address {
			get { return info.Address.GetIPAddress (); }
		}

		public override bool IsDnsEligible {
			get { return info.LengthFlags.IsDnsEligible; }
		}

		public override bool IsTransient {
			get { return info.LengthFlags.IsTransient; }
		}

		// UnicastIPAddressInformation members

		public override long AddressPreferredLifetime {
			get { return info.PreferredLifetime; }
		}

		public override long AddressValidLifetime {
			get { return info.ValidLifetime; }
		}

		public override long DhcpLeaseLifetime {
			get { return info.LeaseLifetime; }
		}

		public override DuplicateAddressDetectionState DuplicateAddressDetectionState {
			get { return info.DadState; }
		}

		public override IPAddress IPv4Mask {
			get {
				Win32_IP_ADAPTER_INFO ai = Win32NetworkInterface2.GetAdapterInfoByIndex (if_index);
				if (ai == null)
					throw new Exception ("huh? " + if_index);
				if (this.Address == null)
					return null;
				string expected = this.Address.ToString ();
				unsafe {
					Win32_IP_ADDR_STRING p = ai.IpAddressList;
					while (true) {
						if (p.IpAddress == expected)
							return IPAddress.Parse (p.IpMask);
						if (p.Next == IntPtr.Zero)
							break;
						p = (Win32_IP_ADDR_STRING) Marshal.PtrToStructure (p.Next, typeof (Win32_IP_ADDR_STRING));
					}

					// Or whatever it should be...
					return null;
				}
			}
		}

		public override PrefixOrigin PrefixOrigin {
			get { return info.PrefixOrigin; }
		}

		public override SuffixOrigin SuffixOrigin {
			get { return info.SuffixOrigin; }
		}
	}
Comment 4 marc 2011-11-17 04:50:32 UTC
I took a look at some Mono sourcecode and extracted some code-snippets to build a helper that returns a the IPv4 subnet mask of the given network interface. The code is not an absolute beauty but it works.


    [StructLayout(LayoutKind.Explicit)]
    struct ifa_ifu
    {
        [FieldOffset(0)]
        public IntPtr ifu_broadaddr;

        [FieldOffset(0)]
        public IntPtr ifu_dstaddr;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct ifaddrs
    {
        public IntPtr ifa_next;
        public string ifa_name;
        public uint ifa_flags;
        public IntPtr ifa_addr;
        public IntPtr ifa_netmask;
        public ifa_ifu ifa_ifu;
        public IntPtr ifa_data;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct sockaddr_in
    {
        public ushort sin_family;
        public ushort sin_port;
        public uint sin_addr;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct sockaddr_in6
    {
        public ushort sin6_family;   /* AF_INET6 */
        public ushort sin6_port;     /* Transport layer port # */
        public uint sin6_flowinfo; /* IPv6 flow information */
        public in6_addr sin6_addr;     /* IPv6 address */
        public uint sin6_scope_id; /* scope id (new in RFC2553) */
    }

    [StructLayout(LayoutKind.Sequential)]
    struct in6_addr
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public byte[] u6_addr8;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct sockaddr_ll
    {
        public ushort sll_family;
        public ushort sll_protocol;
        public int sll_ifindex;
        public ushort sll_hatype;
        public byte sll_pkttype;
        public byte sll_halen;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[] sll_addr;
    }

    internal class IPInfoTools
    {
        const int AF_INET = 2;
        const int AF_INET6 = 10;
        const int AF_PACKET = 17;

        [DllImport("libc")]
        static extern int getifaddrs (out IntPtr ifap);

        [DllImport ("libc")]
        static extern void freeifaddrs (IntPtr ifap);

        internal static string GetIPv4Mask(string networkInterfaceName)
        {
            IntPtr ifap;
            if (getifaddrs(out ifap) != 0)
            {
                throw new SystemException("getifaddrs() failed");
            }

            try
            {
                var next = ifap;
                while (next != IntPtr.Zero)
                {
                    var addr = (ifaddrs)Marshal.PtrToStructure(next, typeof(ifaddrs));
                    var name = addr.ifa_name;

                    if (addr.ifa_addr != IntPtr.Zero)
                    {
                        var sockaddr = (sockaddr_in)Marshal.PtrToStructure(addr.ifa_addr, typeof(sockaddr_in));
                        switch (sockaddr.sin_family)
                        {
                            case AF_INET6:
                                //sockaddr_in6 sockaddr6 = (sockaddr_in6)Marshal.PtrToStructure(addr.ifa_addr, typeof(sockaddr_in6));
                                break;
                            case AF_INET:
                                if (name == networkInterfaceName)
                                {
                                    var netmask = (sockaddr_in)Marshal.PtrToStructure(addr.ifa_netmask, typeof(sockaddr_in));
                                    var ipAddr = new IPAddress(netmask.sin_addr);  // IPAddress to format into default string notation
                                    return ipAddr.ToString();
                                }
                                break;
                            case AF_PACKET:
                                {
                                    var sockaddrll = (sockaddr_ll)Marshal.PtrToStructure(addr.ifa_addr, typeof(sockaddr_ll));
                                    if (sockaddrll.sll_halen > sockaddrll.sll_addr.Length)
                                    {
                                        Console.Error.WriteLine("Got a bad hardware address length for an AF_PACKET {0} {1}",
                                                                sockaddrll.sll_halen, sockaddrll.sll_addr.Length);
                                        next = addr.ifa_next;
                                        continue;
                                    }
                                }
                                break;
                        }
                    }

                    next = addr.ifa_next;
                }
            }
            finally
            {
                freeifaddrs(ifap);
            }

            return null;
        }
    }

Usage of the above helper is like this:

    String subnetMask = IPInfoTools.GetIPv4Mask("etc0");

Another code I had working is the mentioned call to "ifconfig" and parsing its output:

        public string GetIPv4Mask(string nicName)
        {
            const string cmd = "ifconfig";
            var args = nicName;
        
            var output = RunProcessCapturingOutput(cmd, args);
        
            var pos = output.IndexOf("Mask:");
            if (pos > -1)
            {
                var posEnd = output.IndexOf(Environment.NewLine, pos);
        
                return posEnd > -1 ? output.Substring(pos + 5, posEnd - pos - 5) : output.Substring(pos + 5);
            }
        
            return "";
        }

I didn't yet manage to fix this in the Mono sourcecode as one needs to change quite some files in Mono to get the above information from the place where it is queried (LinuxNetworkInterface) to the place where it is used (LinuxUnicastIPAddressInfo). The subnet mask is available in LinuxNetworkInterface.ImplGetAllNetworkInfaces() in the variable addr.ifa_netmask but its not forwarded to the LinuxUnicastIPAddressInformation where one could query it. Maybe some who knows the networking class hierarchy better then I do can take a look how to forward the information to LinuxUnicastIPAddressInformation. I think one would need to do the Linux implementation similar to the Win32 implementation where an info-block is forwarded all the way down - just a guess.
Comment 5 Tom Hindle 2012-07-19 13:15:29 UTC
Created attachment 2231 [details]
Test case reproducing problem.
Comment 6 Tom Hindle 2012-07-19 13:23:55 UTC
Created attachment 2234 [details]
proposed patch
Comment 7 Weeble 2013-03-19 04:58:19 UTC
What's the status of this? Is the patch acceptable? Does it need testing? Is it too out of date?
Comment 8 Alexander Köplinger [MSFT] 2015-12-01 15:47:10 UTC
This should be fixed by https://github.com/mono/mono/pull/2272.
I added the test case from Tom Hindle in https://github.com/mono/mono/commit/dd5fb3546adcdd705a63a2d8c73a6526f1231e1e.

Note You need to log in before you can comment on or make changes to this bug.