How to get IP Address of interface on which UPnP service was found

Sep 20, 2012 at 1:07 AM

I'm trying to auto register a mapping with the UPnP device.

Question is how do I find out what internal IP address to use? There are multiple adapters/subnet - so which is the right interface/subnet on which the service was discovered?

Coordinator
Sep 20, 2012 at 2:26 AM
Edited Sep 20, 2012 at 2:29 AM

rboy1,

There would be two ways of doing this, for the first on you could determine the adapter / subnet yourself by determining which adapter has the connection to the domain for the device document URL located with the property:

 

lsService.Device.RootDevice.DocumentURL

 

Or you could discover the service by passing true to the "resolveNetworkInterfaces", however, this option is only supported under Windows Vista and above, so if you are targeting XP i would use the first method, to do this you need to use a function of the Discovery class which has that parameter, so instead of:

Services lsServices = Discovery.FindServices("urn:schemas-upnp-org:service:WANPPPConnection:1");

Use:

bool lbCompleted;
Services lsServices = Discovery.FindServices("urn:schemas-upnp-org:service:WANPPPConnection:1", 9000, Int32.Max, out lbCompleted, AddressFamilyFlags.IPvBoth, true);
....
if (lsService.Device.RootDevice.InterfaceGuidAvailable)
{
  // Do something with the GUID at lsService.Device.RootDevice.InterfaceGUID using API calls to get the Interface details
}

However, I would definitely recommend doing it yourself using the URL, because the second option has too many points of failure.

What I will do in a future verison is add another method to the Device class which you can call and get the details of the first adapter for which it connects to. Until then you will have to do it yourself unfortunately.

Sep 20, 2012 at 2:52 PM

Thanks, I like the first idea of checking the URL. I need to figure out how to get the interface details in .NET 4 Client framework and what I should compare (i.e. what I'm lookikng for in the URL).

 

If you have any sample code or pointers that would be brilliant!

 

Also, on urn:schemas-upnp-org:service:WANPPPConnection:1

I understand this is the only service string that is used by UPnP, is that correct? What happens if I put "" instead?

Sep 20, 2012 at 3:59 PM

One more questions, the URI urn:schemas-upnp-org:service:WANPPPConnection:1 doesn't seem to exist on my network (I have atleast 2 UPnP Gateway devices on my network).

 

I don't see any standard either published. Any idea how I can proceed? I should I just iterate through all the devices?

More importantly when usint the NATUPnP library I use:

portMapping.Description.ToLower().Contains("xyz")

what's the eq. with this Library?

Coordinator
Sep 20, 2012 at 9:13 PM

I have never this exact thing using the API calls, but you will need to iterate through all adapters and try to connect to the domains IP address of the URL as described, here are some APIs that will you need to use:

http://www.pinvoke.net/search.aspx?search=GetAdaptersInfo&namespace=[All]

http://www.pinvoke.net/default.aspx/Structures/IP_ADAPTER_INFO.html

 

Then you will have to find a way of connecting and specifying which adapter to connect from, if the connection is successful then you can mark the device as being connected to that specific adapter (note, that it may be possible for the device to be connected to multiple adapters in which case you would have to create multiple rules.

"urn:schemas-upnp-org:service:WANPPPConnection:1" is the standard UPnP service used for Point to Point Protocol port mappings (among other things), however, there is also the "urn:schemas-upnp-org:service:WANIPConnection:1" from memory, this service is used to map port mappings as well, but it is linked to the external IP address not the entire device. You certainly can just search for all services if you want (passing null will do this from mmemory) and then enumerate yourself.

I suggest you check out the information at http://www.upnp.org it contains all the standard devices and services.

With this library you can enumerate through mappings by using the GetSpecificPortMapping action on the service, each one will return a plethora of parameters as an array of objects, you could simply write a small function to concatenate an IEnumerable<object> into a single string then call that on the parameters.

Sep 21, 2012 at 1:36 PM

Thanks, this is very helpful.

 

I figured out a way (finally) to get the Adapter info and match it to the UPnP device IP address.

the adapterinfo in C# can be retireved using the NetworkInterface class and then GetIPProperties

http://msdn.microsoft.com/en-us/library/system.net.networkinformation.networkinterface.aspx

 

This part was easy, the hard part was matching the IP Address to the Network Adapter Subnet to ensure they are on the same subnet, because if they aren't then one doesn't want to enable UPnP mapping for that interface. I have written some routines to match the IPv4 and IPv6 subnets (IPv6, just look for a non localsite address flag).

What I'm still struggling with is the format of the DescriptionURL, there doesn't appear to be a standard for that, I'm just assuming every device has a format of http://x.x.x.x:y/.... from which I'm extracting x.x.x.x

The question is, have you seen any other types of formats? This is what can mess things up.

I've also found that the GetSpecificPortMapping isn't reliable, it's just better to directly issue a AddPort or DeletePort action.

 

Coordinator
Sep 21, 2012 at 2:03 PM

the format of the DescriptionURL is a standard URI format, so simply use the C# URI class to decode the URI and extract the host name part only:

Uri luURI = new Uri(lsDescriptionURL);

string luHostName = luUri.Host;

        // Then use DNS to convert lsHostName into IP address (be aware that in SOME devices this may already be an IP Address, but other devices add their own network name and use that - so you will need to resolve the Host name to an IP Address)

 

The GetSpecificPortMapping entry should be implemented properly by the UPnP device, however, older devices dont seem to implement the UPnP protocols as they should so sometimes this is the case, adding the port and checking to make sure it hasnt error is a good way to do it as well. Also check out the GetGenericPortMapping action (one is for static mappings and one is for UPnP mappings, I cant remember which is which at the moment).

 

Keep me updated :)

Coordinator
Sep 21, 2012 at 2:10 PM

I just thought of an easier way to do this, once you have the host name simply use the IPEndPoint and Socket to determine the local IP address from which a remote address is available, see here:

 

http://stackoverflow.com/questions/3138885/how-to-get-an-ip-address-from-socket

Sep 21, 2012 at 7:47 PM

Okay so here is the code I'm using for everyone's benefit:

 

I've made a static class out of it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.NetworkInformation;
using System.Net;

using ManagedUPnP;
using ManagedUPnP.Descriptions;
using MCEBuddy.Util;

namespace MCEBuddy.Engine
{
    public static class UPnP
    {
        /// <summary>
        /// Enabled NAT Forwarding on all UPnP devices on the network (and opens Firewall) for the specified port on the local machine
        /// </summary>
        /// <param name="onPort">Port number to map</param>
        /// <param name="verbose">Write detailed logs</param>
        public static void EnableUPnP(int onPort, bool verbose)
        {
            try
            {
                bool searchCompleted;
                Services lsServices = Discovery.FindServices(null, 30000, 999, out searchCompleted, AddressFamilyFlags.IPvBoth, false);

                // Check for an incomplete search
                if (!searchCompleted)
                {
                    Log.AppLog.WriteEntry("UPnP", "UPnP search incomplete, retrying again", Log.LogEntryType.Warning, true);
                    lsServices = Discovery.FindServices(null, 30000, 999, out searchCompleted, AddressFamilyFlags.IPvBoth, false);

                    if (!searchCompleted)
                        Log.AppLog.WriteEntry("UPnP", "UPnP search incomplete, UPnP enablement may not succeed", Log.LogEntryType.Warning, true);
                }

                foreach (ManagedUPnP.Service lsService in lsServices)
                {
                    ServiceDescription lsdDesc = lsService.Description();
                    if (lsdDesc.Actions.ContainsKey("AddPortMapping")) // Check to see if is a WAN UPnP device that supports Port Mappings, if we so need to enable Port Forwarding for each such device
                    {
                        object[] inParams;
                        Uri luURI;
                        IPAddress uPnPAddress; // IP address of uPnP device

                        luURI = new Uri(lsService.Device.RootDevice.DocumentURL); // This is the Device description containing the Interface/IP Address of device
                        uPnPAddress = IPAddress.Parse(luURI.Host);

                        try
                        {
                            inParams = new object[] { "", onPort, "tcp" };
                            lsService.InvokeAction("DeletePortMapping", inParams); // Delete the port mapping (we will create a fresh one later)
                        }
                        catch (Exception e)
                        {
                            if (verbose)
                            {
                                Log.AppLog.WriteEntry("UPnP", "Unable to Delete Port Mapping from " + luURI.Host, Log.LogEntryType.Warning, true);
                                Log.AppLog.WriteEntry("UPnP", "Delete Error " + e.ToString(), Log.LogEntryType.Warning, true);
                            }
                        }

                        // Find the adapter/network interface/subnet on which this device is located
                        NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
                        foreach (NetworkInterface adapter in nics)
                        {
                            IPInterfaceProperties properties = adapter.GetIPProperties();
                            UnicastIPAddressInformationCollection adapterIPAddress = properties.UnicastAddresses;
                            foreach (UnicastIPAddressInformation address in adapterIPAddress)
                            {
                                // Check if the interface ip address and upnp device ip address are in the same subnet/domain
                                if ((address.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) && (uPnPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)) // Check for IPv4 here
                                {
                                    if (address.IPv4Mask != null) // Auto IP address have a null subnet mask
                                    {
                                        if (IPAddressExtensions.IsInSameSubnet(address.Address, uPnPAddress, address.IPv4Mask))// are they in the same subnet
                                        {
                                            try
                                            {
                                                // Now we add the port mapping for the current MCEBuddy server
                                                inParams = new object[] { "", onPort, "tcp", onPort, address.Address.ToString(), true, "mcebuddy2x", 0 };
                                                lsService.InvokeAction("AddPortMapping", inParams);
                                            }
                                            catch (Exception e)
                                            {
                                                if (verbose)
                                                {
                                                    Log.AppLog.WriteEntry("UPnP", "Unable to Add IPv4 Port Mapping to " + luURI.Host, Log.LogEntryType.Warning, true);
                                                    Log.AppLog.WriteEntry("UPnP", "Add Error " + e.ToString(), Log.LogEntryType.Warning, true);
                                                }
                                            }
                                        }
                                    }
                                }
                                else if ((address.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) && (uPnPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)) // Check for IPv6 here
                                {
                                    // TODO: Are we checking for all the IPv6 address types?
                                    if (!(address.Address.IsIPv6LinkLocal || uPnPAddress.IsIPv6LinkLocal)) // Check for IPv6 non local address here
                                    {
                                        try
                                        {
                                            // Now we add the port mapping for the current MCEBuddy server
                                            inParams = new object[] { "", onPort, "tcp", onPort, address.Address.ToString(), true, "mcebuddy2x", 0 };
                                            lsService.InvokeAction("AddPortMapping", inParams);
                                        }
                                        catch (Exception e)
                                        {
                                            if (verbose)
                                            {
                                                Log.AppLog.WriteEntry("UPnP", "Unable to Add IP6 Port Mapping to " + luURI.Host, Log.LogEntryType.Warning, true);
                                                Log.AppLog.WriteEntry("UPnP", "Add Error " + e.ToString(), Log.LogEntryType.Warning, true);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Log.AppLog.WriteEntry("UPnP", "Error trying to enable UPnP -> " + e.ToString(), Log.LogEntryType.Warning, true);
            }
        }

        /// <summary>
        /// Disable NAT Forwarding on all UPnP devices on the network for the specified port on the local machine
        /// </summary>
        /// <param name="onPort">Port number to map</param>
        /// <param name="verbose">Write detailed logs</param>
        public static void DisableUPnP(int onPort, bool verbose)
        {
            try
            {
                bool searchCompleted;
                Services lsServices = Discovery.FindServices(null, 30000, 999, out searchCompleted, AddressFamilyFlags.IPvBoth, false);

                foreach (ManagedUPnP.Service lsService in lsServices)
                {
                    ServiceDescription lsdDesc = lsService.Description();
                    if (lsdDesc.Actions.ContainsKey("AddPortMapping")) // Check to see if is a WAN UPnP device that supports Port Mappings, if we so need to disable Port Forwarding for each such device
                    {
                        object[] inParams;

                        Uri luURI = new Uri(lsService.Device.RootDevice.DocumentURL); // This is the Device description containing the Interface/IP Address of device

                        try
                        {
                            inParams = new object[] { "", onPort, "tcp" };
                            lsService.InvokeAction("DeletePortMapping", inParams); // Delete the port mapping (we will create a fresh one later)
                        }
                        catch (Exception e)
                        {
                            if (verbose)
                            {
                                Log.AppLog.WriteEntry("UPnP", "Unable to Delete Port Mapping from " + luURI.Host, Log.LogEntryType.Warning, true);
                                Log.AppLog.WriteEntry("UPnP", "Delete Error " + e.ToString(), Log.LogEntryType.Warning, true);
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Log.AppLog.WriteEntry("UPnP", "Error trying to disable UPnP -> " + e.ToString(), Log.LogEntryType.Warning, true);
            }
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;

namespace MCEBuddy.Util
{
    public static class IPAddressExtensions
    {
        /// <summary>
        /// Returns the broadcast address from an IPAddress and Subnet Mask
        /// </summary>
        public static IPAddress GetBroadcastAddress(this IPAddress address, IPAddress subnetMask)
        {
            byte[] ipAdressBytes = address.GetAddressBytes();
            byte[] subnetMaskBytes = subnetMask.GetAddressBytes();

            if (ipAdressBytes.Length != subnetMaskBytes.Length)
                throw new ArgumentException("Lengths of IP address and subnet mask do not match.");

            byte[] broadcastAddress = new byte[ipAdressBytes.Length];
            for (int i = 0; i < broadcastAddress.Length; i++)
            {
                broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
            }
            return new IPAddress(broadcastAddress);
        }

        /// <summary>
        /// Returns the Network Address (Subnet) from an IPAddress and Subnet
        /// </summary>
        public static IPAddress GetNetworkAddress(this IPAddress address, IPAddress subnetMask)
        {
            byte[] ipAdressBytes = address.GetAddressBytes();
            byte[] subnetMaskBytes = subnetMask.GetAddressBytes();

            if (ipAdressBytes.Length != subnetMaskBytes.Length)
                throw new ArgumentException("Lengths of IP address and subnet mask do not match.");

            byte[] broadcastAddress = new byte[ipAdressBytes.Length];
            for (int i = 0; i < broadcastAddress.Length; i++)
            {
                broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
            }
            return new IPAddress(broadcastAddress);
        }

        /// <summary>
        /// Compares if 2 IP addresses are within the same Subnet using the Subnet Mask
        /// </summary>
        /// <returns>True if they are part of the same subnet</returns>
        public static bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
        {
            IPAddress network1 = address1.GetNetworkAddress(subnetMask);            
            IPAddress network2 = address2.GetNetworkAddress(subnetMask);

            return network1.Equals(network2);
        }
    }
}

Sep 21, 2012 at 7:48 PM
thetoid wrote:

I have never this exact thing using the API calls, but you will need to iterate through all adapters and try to connect to the domains IP address of the URL as described, here are some APIs that will you need to use:

http://www.pinvoke.net/search.aspx?search=GetAdaptersInfo&namespace=[All]

http://www.pinvoke.net/default.aspx/Structures/IP_ADAPTER_INFO.html

 

Then you will have to find a way of connecting and specifying which adapter to connect from, if the connection is successful then you can mark the device as being connected to that specific adapter (note, that it may be possible for the device to be connected to multiple adapters in which case you would have to create multiple rules.

"urn:schemas-upnp-org:service:WANPPPConnection:1" is the standard UPnP service used for Point to Point Protocol port mappings (among other things), however, there is also the "urn:schemas-upnp-org:service:WANIPConnection:1" from memory, this service is used to map port mappings as well, but it is linked to the external IP address not the entire device. You certainly can just search for all services if you want (passing null will do this from mmemory) and then enumerate yourself.

I suggest you check out the information at http://www.upnp.org it contains all the standard devices and services.

With this library you can enumerate through mappings by using the GetSpecificPortMapping action on the service, each one will return a plethora of parameters as an array of objects, you could simply write a small function to concatenate an IEnumerable<object> into a single string then call that on the parameters.


BTW passing null to FindServices causes an exception.

Coordinator
Sep 21, 2012 at 9:15 PM
Edited Sep 21, 2012 at 9:20 PM

I couldnt get it to error with the null on FindServices, which exact overload are you using?

Thanks for the code, do you mind if I integrate the local IP detection code into the framework?

Coordinator
Sep 21, 2012 at 10:08 PM

rboy1, when have some spare time, would you mind posting a review of Managed UPnP for me, thank you :D

Sep 21, 2012 at 10:41 PM

If I use FindServices(null); it given an exception.

 

Go ahead and use the code, it's part of another Opensource project on CodePlex called MCEBuddy which I'm developing, feel free to link your project to MCEBuddy and i'll do the same.

http://mcebuddy2x.codeplex.com

 

I also found a bug in the logic for IPv6 enablement. Maybe you can help me out here. The current logic states that if the Remote UPnP device and local network interface are NOT using Ipv6 Link Local addressing (i.e. either Site Local or Global) then go ahead and enable UPnP.

if (!(address.Address.IsIPv6LinkLocal || uPnPAddress.IsIPv6LinkLocal)) // Check for IPv6 non local address here

Well, if the local machine has a Global ipv6 address then it won't need UPnP right? So what's the correct logic/mechanism here. I'm a little busted trying to figure out when UPnP works in a IPv6 environment (if at all?).

Sep 21, 2012 at 10:46 PM
Edited Sep 21, 2012 at 10:46 PM

Under Edit Project Profile, at the end Add Related Projects you can enter

MCEBuddy2x

 

It should pull up the project automatically

Sep 22, 2012 at 12:14 AM
rboy1 wrote:

If I use FindServices(null); it given an exception.

 

Go ahead and use the code, it's part of another Opensource project on CodePlex called MCEBuddy which I'm developing, feel free to link your project to MCEBuddy and i'll do the same.

http://mcebuddy2x.codeplex.com

 

I also found a bug in the logic for IPv6 enablement. Maybe you can help me out here. The current logic states that if the Remote UPnP device and local network interface are NOT using Ipv6 Link Local addressing (i.e. either Site Local or Global) then go ahead and enable UPnP.

if (!(address.Address.IsIPv6LinkLocal || uPnPAddress.IsIPv6LinkLocal)) // Check for IPv6 non local address here

Well, if the local machine has a Global ipv6 address then it won't need UPnP right? So what's the correct logic/mechanism here. I'm a little busted trying to figure out when UPnP works in a IPv6 environment (if at all?).


Okay I think I've figured it out, if the IPv6 devices are on the same Network (48 bits) + subnet (16 bits)., the first 64 bits are used to identify if they are on the same subnet really, ie. then it would make sense to have UPnP else they are either unquiely addressable or not reachable.

This code should work:

 

                                    // TODO: How do we check when to enable UPnP for IPv6, for now check network 64 bits (or should one check only 16 bits of subnet)?
                                    BigInteger uPnPIPv6 = new BigInteger(uPnPAddress.GetAddressBytes().Reverse().ToArray());
                                    BigInteger localIPv6 = new BigInteger(address.Address.GetAddressBytes().Reverse().ToArray());
                                    
                                    // Network Address Range, In IPv6, the first 48 bits are for Internet routing.
                                    // IPv6 Subnetting Range, The 16 bits from the 49th to the 54th are for defining subnets.
                                    // The binary mask just for the subnetting range would be 1111110000000000 which translates to a hex value of FC00
                                    // Some IPv6 masking tools will work with just this one hex word, otherwise a full 128-bit hex mask would be FFFF:FFFF:FFFF:FC00:000:0000:0000:0000
                                    BigInteger ipv6SubnetMask = new BigInteger(0xFFFFFFFFFFFFFC00) << 64; // (using the full 128bit hex mask, comparing the first 64 bits, 48 network + 16 subnet)
                                    
                                    if ((uPnPIPv6 & ipv6SubnetMask) == (localIPv6 & ipv6SubnetMask)) // Check if they are in the same subnet
                                    {

 

 
 

Sep 22, 2012 at 12:38 AM
rboy1 wrote:
rboy1 wrote:

If I use FindServices(null); it given an exception.

 

Go ahead and use the code, it's part of another Opensource project on CodePlex called MCEBuddy which I'm developing, feel free to link your project to MCEBuddy and i'll do the same.

http://mcebuddy2x.codeplex.com

 

I also found a bug in the logic for IPv6 enablement. Maybe you can help me out here. The current logic states that if the Remote UPnP device and local network interface are NOT using Ipv6 Link Local addressing (i.e. either Site Local or Global) then go ahead and enable UPnP.

if (!(address.Address.IsIPv6LinkLocal || uPnPAddress.IsIPv6LinkLocal)) // Check for IPv6 non local address here

Well, if the local machine has a Global ipv6 address then it won't need UPnP right? So what's the correct logic/mechanism here. I'm a little busted trying to figure out when UPnP works in a IPv6 environment (if at all?).


Okay I think I've figured it out, if the IPv6 devices are on the same Network (48 bits) + subnet (16 bits)., the first 64 bits are used to identify if they are on the same subnet really, ie. then it would make sense to have UPnP else they are either unquiely addressable or not reachable.

This code should work:

 

 

 

                                    // TODO: How do we check when to enable UPnP for IPv6, for now check network 64 bits (or should one check only 16 bits of subnet)?
                                    BigInteger uPnPIPv6 = new BigInteger(uPnPAddress.GetAddressBytes().Reverse().ToArray());
                                    BigInteger localIPv6 = new BigInteger(address.Address.GetAddressBytes().Reverse().ToArray());
                                    
                                    // Network Address Range, In IPv6, the first 48 bits are for Internet routing.
                                    // IPv6 Subnetting Range, The 16 bits from the 49th to the 54th are for defining subnets.
                                    // The binary mask just for the subnetting range would be 1111110000000000 which translates to a hex value of FC00
                                    // Some IPv6 masking tools will work with just this one hex word, otherwise a full 128-bit hex mask would be FFFF:FFFF:FFFF:FC00:000:0000:0000:0000
                                    BigInteger ipv6SubnetMask = new BigInteger(0xFFFFFFFFFFFFFC00) << 64; // (using the full 128bit hex mask, comparing the first 64 bits, 48 network + 16 subnet)
                                    
                                    if ((uPnPIPv6 & ipv6SubnetMask) == (localIPv6 & ipv6SubnetMask)) // Check if they are in the same subnet
                                    {

 

 
 
 


Okay my bad, i should have gone through the UPnP specfication, it says the host has to be of type x.x.x.x

so UPnP devices do not support UPnP NAT forwarding. i.e. the above code for Ipv6 should dissapear and only ipv4 remain.

 

to your other question:

GenericPortMapping also gives trouble, I have a router with Dynamic Mappings so I can't test static maps, but I can say dynamic is a huge problem. For 1 I need to restart the port scan each time I delete/add and then also I get random errors from the router.

And I have a new XFinity router

Coordinator
Sep 22, 2012 at 5:19 AM

First of all, thanks for the great review, I will take your comments on board! :)

 

Yes thats correct the current implementations of UPnP are for local routing only not WAN, and therefore IPv6 support isnt really required, however, I think this document descibres a new version of UPnP 1.1 (which is probably seldomly implemented) that support IPv6 routing:

http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1-AnnexA.pdf

But for the foreseeable future im sure IPv6 UPnP discovery support will NOT be needed, i should have thought of this.

 

Thats strange that the GetGenericPortMapping dosnt work very well with a new router such as this, sometimes the firmware developers for these devices get very lazy and only implement some of the UPnP device functions and not others. I have found the new Netgear routers to be pretty good with UPnP but their older ones (the DG834's) were terrible!

Coordinator
Sep 22, 2012 at 7:51 AM

I forgot to ask, where is the exception occurring when you use null in the FindServices function, because I used null for both of the overloads and no errors were produced? It would be great if you could debug using the source code and find the line where the null exception is occurring. Thanks.

Coordinator
Sep 22, 2012 at 12:23 PM
Edited Sep 22, 2012 at 12:26 PM

VBDotNetLover,

Thanks for that posting, however, my framework actually uses many of the different functions of the UPnP End Point API (including FindByType("upnp:rootdevice"), depending on how you call it, ie. null string or empty string vs service or device identifier, this is all handled internally automatically and also done asynchronously if requested, that is the point of the framework.

Also, this thread is about how to use my framework to get the current machines network interface IP Address for which a device is connected to, not how to use the UPnP Endpoint API directly to find devices, please create another thread if you wish to discuss the above.

Thanks,

Toid

Sep 22, 2012 at 1:08 PM
Edited Sep 22, 2012 at 1:39 PM

Ok, sorry about that, If you would like, I can post my upnp.bas module so anyone can pick and choose what they'd like from it, no problem. Also, would you like me to go ahead and Delete this and my previous message on this thread? Or, if you can, feel free to Delete both this and my previous message with the GetAllDevices function, as I don't think I have the permissions/user rights to do a proper deletion (I can just edit). Once again, very sorry.

 

Coordinator
Sep 22, 2012 at 7:19 PM
Edited Sep 22, 2012 at 7:20 PM

Cheers, thanks for that mate, thanks for editing it  :)

feel free to create another discussion thread if you like for this topic! :)

Coordinator
Sep 22, 2012 at 10:10 PM

rboy1,

A new version of ManagedUPnP has been released, incorporating properties on the Device for getting the NICs, UnicastAddressInformations and the IPAddresses of connectable interfaces for a device. I did end up implementing the IPv6 stuff as well, because the UPnP standard V1.1 does support it, and with my setup at home, my other Win 7 64 machines' Windows Media Player actually gets detected through the IPv6 interfaces.

Have a look when you get the chance. 

Cheers,

TheToid

Sep 23, 2012 at 4:52 AM
thetoid wrote:

I forgot to ask, where is the exception occurring when you use null in the FindServices function, because I used null for both of the overloads and no errors were produced? It would be great if you could debug using the source code and find the line where the null exception is occurring. Thanks.


Okay when I use FindServices(null), here is what I get:

 

WARNING> 2012-09-23T00:45:56 UPnP --> Delete Error ManagedUPnP.UPnPException: UPnP Error #-2147220622: -2147220622 ---> System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x80040372
   at UPNPLib.IUPnPService.InvokeAction(String bstrActionName, Object vInActionArgs, Object& pvOutActionArgs)
   at ManagedUPnP.Service.InvokeAction(String name, Object[] inParams) in D:\MCEBuddy\MCEBuddy 2.x\MCEBuddy.ManagedUPnP\Service.cs:line 385
   --- End of inner exception stack trace ---
   at ManagedUPnP.Service.InvokeAction(String name, Object[] inParams) in D:\MCEBuddy\MCEBuddy 2.x\MCEBuddy.ManagedUPnP\Service.cs:line 400
   at MCEBuddy.Engine.UPnP.EnableUPnP(Int32 onPort, Boolean verbose) in D:\MCEBuddy\MCEBuddy 2.x\MCEBuddy.Engine\UPnP.cs:line 54
WARNING> 2012-09-23T00:48:03 UPnP --> Error trying to disable UPnP -> System.NullReferenceException: Object reference not set to an instance of an object.
   at UPNPLib.IUPnPDeviceFinder.FindByType(String bstrTypeURI, UInt32 dwFlags)
   at ManagedUPnP.Discovery.FindNativeDevices(String deviceOrServiceType, AddressFamilyFlags addressFamily) in D:\MCEBuddy\MCEBuddy 2.x\MCEBuddy.ManagedUPnP\Discovery.cs:line 318
   at ManagedUPnP.Discovery.FindServices(String serviceType) in D:\MCEBuddy\MCEBuddy 2.x\MCEBuddy.ManagedUPnP\Discovery.cs:line 521
   at MCEBuddy.Engine.UPnP.DisableUPnP(Int32 onPort, Boolean verbose) in D:\MCEBuddy\MCEBuddy 2.x\MCEBuddy.Engine\UPnP.cs:line 150

Sep 23, 2012 at 4:53 AM
thetoid wrote:

rboy1,

A new version of ManagedUPnP has been released, incorporating properties on the Device for getting the NICs, UnicastAddressInformations and the IPAddresses of connectable interfaces for a device. I did end up implementing the IPv6 stuff as well, because the UPnP standard V1.1 does support it, and with my setup at home, my other Win 7 64 machines' Windows Media Player actually gets detected through the IPv6 interfaces.

Have a look when you get the chance. 

Cheers,

TheToid


Thanks, can you let me konw what API's are used to access it and how they should be used

Coordinator
Sep 23, 2012 at 5:06 AM
Edited Sep 23, 2012 at 5:12 AM

There are 6 new properties available on the Device object:

RootHostName - Gets the host name of the Root Device Document URL (this will usually be an IP address - but may be a host name for some devices)

RootHostAddresses - Gets the IP address of the Root Device Document URL - will be the same as Root Host Name if the Host Name is an IP Address, otherwise it will be resolved IP address based on the Host Name

Adapters - Gets an array of all Network Interfaces for which this device is connected to based on the InterfaceGuid first, and then the NICS which are on the same subnet as the RootHostAddresses if InterfaceGuidAvailable is false (ie. resolveNetworkInterfaces was false when discovered or on Windows XP)

AdapterUnicastIPAddressInformations  - Gets an array of all UnicastIPAddressInformation for which this device is connected to based on the InterfaceGuid first, and then the NICS which are on the same subnet as the RootHostAddresses if InterfaceGuidAvailable is false (ie. resolveNetworkInterfaces was false when discovered or on Windows XP)

AdapterIPAddresses  - Gets an array of all IPAddresses for which this device is connected to based on the InterfaceGuid first, and then the NICS which are on the same subnet as the RootHostAddresses if InterfaceGuidAvailable is false (ie. resolveNetworkInterfaces was false when discovered or on Windows XP)

So, if lsService is your service then you could access the AdapterIPAddresses for it by using:

lsService.Device.AddapterIPAddresses

Its important to note that if you make sure you specify resolveNetworkInterfaces when doing the discovery and you are running on Windows Vista or higher than the AdapterIPAddresses will be quicker to resolve, have a look in the Device.cs class in the source code for more clarification on how this works. Also check out the IPAddressExtensions.cs file in the source code for how I checked the Subnet for the IPv6, it would be nice if you could do a little QC on it to make sure its working right.

Let me know how you go :)

And thanks for the exception list, ill see if I can replicate, looks pretty straight forward that exception so im not sure why mine didnt do it the other night.

Sep 23, 2012 at 5:30 AM

I'm assuming your logic for checking ipv6 subnet masks is correct (though I'm trying to figure out what this 54 is about).

 

However the question to my mind:

address.IsIPv6LinkLocal && fromAddress.Address.IsIPv6LinkLocal

 

Why a restriction on LinkLocal? What about Site local and Global addresses. Link local could be auto configured so it's quite likely they weren't assigned and is the default address which may not be mapped (or it could be auto configured).

Sometimes network operators also assign global addresses to a private ipv6 networks but on the gateway it does ipv6 to ipv4 mappings and it may do a NAT on it. So really the quetsion is, can it be assumed only link local addresses need to be NATTED?

Sep 23, 2012 at 5:48 AM
rboy1 wrote:

I'm assuming your logic for checking ipv6 subnet masks is correct (though I'm trying to figure out what this 54 is about).

 

However the question to my mind:

address.IsIPv6LinkLocal && fromAddress.Address.IsIPv6LinkLocal

 

Why a restriction on LinkLocal? What about Site local and Global addresses. Link local could be auto configured so it's quite likely they weren't assigned and is the default address which may not be mapped (or it could be auto configured).

Sometimes network operators also assign global addresses to a private ipv6 networks but on the gateway it does ipv6 to ipv4 mappings and it may do a NAT on it. So really the quetsion is, can it be assumed only link local addresses need to be NATTED?


From the specs:

The following requirements apply to devices and control points using the UPnP Device Architecture over IPv6:

  1. Devices and control points MUST obtain a link-local address according to RFC 2462. Devices and control points SHOULD use the same address as previously used whenever possible. Appendix A.3.2, "Short overview of protocol specified by RFC 2462" presents a short overview of the theory of operation as specified by RFC 2462.
  2. Devices and control points MUST try to obtain (this may fail) a global address in each advertised prefix with the A bit set according to RFC 2462 (stateless autoconfiguration) and MAY try to obtain an IP address using DHCPv6, according to RFC 3315 (stateful autoconfiguration, DHCPv6). Devices and control points SHOULD use the same address as previously used whenever possible. A device MAY decide not to use its link-local addresses for UPnP if it supports UPnP on at least one global address on the same interface. This reduces overhead involved with announcing the device on all its addresses. A device MUST offer UPnP on one of its global addresses if it offers UPnP on a link-local address and it has successfully obtained a global address. This promotes interoperability and visibility of the UPnP device in larger networks.
  3. Devices and control points MUST listen for multicast messages on link local scope FF02::C, site local scope FF05::C and global scope FF0E::C (even if the device or control point does not have a global address,it MUST listen to all scopes). See also RFC 4291 for scope definitions.

  1. Devices MUST multicast SSDP messages for each of the UPnP-enabled interfaces. The scope of multicast SSDP messages MUST be link local FF02::C if the message is sent from a link local address. If the message is sent from a global address it MUST be multicast using either global scope FF0E::C or site local scope FF05::C. In networks with complex topologies and overlapping sites, use of global scope is RECOMMENDED.
  2. The hop limit of each IP packet for a global scope multicast message with a permanently assigned multicast address (e.g. FF0E::C for SSDP and FF0E::130 for multicast eventing) SHOULD be set to 254 and SHOULD be configurable.
  3. Devices and control points MAY select the interface or interfaces over which UPnP is enabled, when the device or control point supports multiple interfaces.
  4. ces MUST listen for unicast SSDP traffic on all its UPnP-enabled addresses.
Coordinator
Sep 23, 2012 at 6:10 AM

Ahh yes, sorry I was getting confused, find services dosnt allow a global search, to do a global search use the other overload, the one with all the parameters. I will add another overload in the next version which does this:

 

        /// <summary>
        /// Finds all services synchronously.
        /// </summary>
        /// <param name="addressFamily">The address family to search in. (Vista or above only).</param>
        /// <param name="resolveNetworkInterfaces">True to resolve network interface Guids.</param>
        /// <returns>A Services list containing the found services.</returns>
        public static Services FindServices(
            AddressFamilyFlags addressFamily = AddressFamilyFlags.IPvBoth,
            bool resolveNetworkInterfaces = false)
        {
            bool lbCompleted = true;

            return FindServices(
                null, int.MaxValue, int.MaxValue, 
                out lbCompleted, 
                addressFamily, resolveNetworkInterfaces); 
        }

This will then just find all services, I will also add a NULL, EMPTY value check on the other overload for a proper exception.

Thanks for pointing this out mate!

Coordinator
Sep 23, 2012 at 6:26 AM

The 54 bits is taken from here:

http://www.exabyte.net/lambert/subnet/ipv6_subnet_masking.htm

In standard setups, the first 48 bits of an address are used for internet routing, so for our purposes we can include them as part of the subnet (which should always be the same anyway), the next 6 bits are used for local subnet addressing, this in total makes for 54 bits, equivalent to an IPv6 subnet mask of:

FFFF:FFFF:FFFF:FC00:0:0:0:0.

Is that right or have I got it by the balls? :)

"Devices and control points MUST obtain a link-local address according to RFC 2462. Devices and control points SHOULD use the same address as previously used whenever possible."

Dosnt this mean they will always have to be link-local, both on the Device and Control Points, this is why I have included the check, do you think I should remove it, I dont have a problem with removing it if you think it would be better that way, its probably better to get false positives than false negatives anyway.

Sep 23, 2012 at 4:51 PM
thetoid wrote:

The 54 bits is taken from here:

http://www.exabyte.net/lambert/subnet/ipv6_subnet_masking.htm

In standard setups, the first 48 bits of an address are used for internet routing, so for our purposes we can include them as part of the subnet (which should always be the same anyway), the next 6 bits are used for local subnet addressing, this in total makes for 54 bits, equivalent to an IPv6 subnet mask of:

FFFF:FFFF:FFFF:FC00:0:0:0:0.

Is that right or have I got it by the balls? :)

"Devices and control points MUST obtain a link-local address according to RFC 2462. Devices and control points SHOULD use the same address as previously used whenever possible."

Dosnt this mean they will always have to be link-local, both on the Device and Control Points, this is why I have included the check, do you think I should remove it, I dont have a problem with removing it if you think it would be better that way, its probably better to get false positives than false negatives anyway.


1. Yes that's what I had also used in my code, but I had made it simpler by using BigInteger and doing a simple mask operation to extract the subnet. I'm guessing your code is doing something similar though I haven't been able to review it in detail (I'm assuming you've done that with sample data)

2. You're correct, it should have a Link Local address, however when I read point 2 about getting a Global Address - if it doesn't fail, then the link local address will be invalid (I don't think it can have 2 ipv6 addresses on a device interface), in which case the mapping will fail (even though it has a global address there is no guarantee that it doesn' sit behind a ipv4 to ipv6 tunnel interface so the NAT may still need to be done). Finally what about addressing the multicast interface requirements?

Coordinator
Sep 23, 2012 at 8:59 PM
Edited Sep 23, 2012 at 9:08 PM

Actually I was just having a look at the information on IPv6 in the wikipedia article:

http://en.wikipedia.org/wiki/IPv6_address

 

And in there it states that the 56 bit network address is recommended for ISPs to assign to their end sites:

"In March 2011, the IETF refined their recommendations for allocation of address blocks to end sites.[14] Instead of assigning either a <tt>/48</tt><tt>/64</tt>, or <tt>/128</tt> (according to IAB's andIESG's views of 2001),[46] Internet service providers should consider assigning smaller blocks (for example a <tt>/56</tt>) to end users. The ARINRIPE & APNIC regional registries' policies encourage <tt>/56</tt> assignments where appropriate."

 

Therefore using /56 for our purposes is probably counter-intuitive as most link-local address will be the standard 64 bit network address. Oops...

 

1. Its just a personal preference really, I dont like BigInteger that much and would prefer to implement the bitwise comparison on my own terms, that way I know exactly whats going on. But yes, basically it creates the bytes required for a specific number of bits as the network prefix and then performs a bitwise comparison on each byte, much the same as the code you pasted in here earlier for the IPv4 subnet check (actually its identical to that code LOL).

2. Yes, you are right, especially due to when it says: " A device MAY decide not to use its link-local addresses for UPnP if it supports UPnP on at least one global address on the same interface.". So that to me reads that the link-local address can be discarded if it gets a global address. Maybe we should check to ensure that both addresses are either link-local or global, or maybe just remove the check all torgether? what do you think.

3. We can simply include FF02::C, FF05::C, FF0E::C manually, ie. if either the local interface or the device IPv6 address match any of these then consider them to be connected as well? What do you think?

Actually that probably wont be sufficient either, the problem is that it isnt really possible to determine if two IPv6 addresess are connectable simply by their address information, and I guess that IPv4 has the same issue, but becuase IPv4 is simpler and generally, networks on IPv4 are setup so that the same subnet mask is usually not used multiple times for different networks at the same site means that using the mask for IPv4 addresses should be pretty reliable.

However doing the same with IPv6 address is never going to be as reliable, so im thinking, maybe we should just try to connect to the device on the smae IP address and port as specified in its documentURL instead of trying to match up the IPs by the address bytes, after all, that is really the only way to determine if a device is connectable on a certain interface for sure.

I am a little concerned as to the amount of time required to perform this connection, especially if the device is not there however. 

Coordinator
Sep 23, 2012 at 9:14 PM
Edited Sep 23, 2012 at 9:35 PM

Because we can only really estimate it, how about a generic check such as this:

            // If its an IPv6 address and both IP addresses are link local and they are on the same subnet
            if (address.AddressFamily == AddressFamily.InterNetworkV6 &&
                fromAddress.Address.AddressFamily == AddressFamily.InterNetworkV6 &&
                (address.IsIPv6LinkLocal || address.IsIPv6SiteLocal) &&
                (fromAddress.Address.IsIPv6LinkLocal || address.IsIPv6SiteLocal) &&
                address.SameSubnetAs(fromAddress.Address, 64))
                return true;

or maybe just remove the type check altogether:

 

            // If its an IPv6 address and both IP addresses are link local and they are on the same subnet
            if (address.AddressFamily == AddressFamily.InterNetworkV6 &&
                fromAddress.Address.AddressFamily == AddressFamily.InterNetworkV6 &&
                address.SameSubnetAs(fromAddress.Address, 64))
                return true;

 

I really dont want to spend a lot of time on this part of the UPnP software, and I would prefer false positives over false negatives when it comes to this stuff, so maybe just leaving at this and letting the programmer decide what to do with it after this point is the best way to handle it.

Dont forget that this code will only be used on XP machines anyway, because the programmer is much better specifying true to ResolveNetworkInterfaces and getting the ACTUAL network with which the device is contactable rather than all interfaces on the same subnet (which may not necessarily be the same network.

However, im open to suggestions so if you find a better way, please post it and I will use that instead! :D

Until I see something better though, these functions will only support LinkLocal and SiteLocal addresses (and maybe Global addresses with the second example), which will cover most setups for now.

Sep 24, 2012 at 2:50 AM
thetoid wrote:

Because we can only really estimate it, how about a generic check such as this:

            // If its an IPv6 address and both IP addresses are link local and they are on the same subnet
            if (address.AddressFamily == AddressFamily.InterNetworkV6 &&
                fromAddress.Address.AddressFamily == AddressFamily.InterNetworkV6 &&
                (address.IsIPv6LinkLocal || address.IsIPv6SiteLocal) &&
                (fromAddress.Address.IsIPv6LinkLocal || address.IsIPv6SiteLocal) &&
                address.SameSubnetAs(fromAddress.Address, 64))
                return true;

or maybe just remove the type check altogether:

 

            // If its an IPv6 address and both IP addresses are link local and they are on the same subnet
            if (address.AddressFamily == AddressFamily.InterNetworkV6 &&
                fromAddress.Address.AddressFamily == AddressFamily.InterNetworkV6 &&
                address.SameSubnetAs(fromAddress.Address, 64))
                return true;

 

I really dont want to spend a lot of time on this part of the UPnP software, and I would prefer false positives over false negatives when it comes to this stuff, so maybe just leaving at this and letting the programmer decide what to do with it after this point is the best way to handle it.

Dont forget that this code will only be used on XP machines anyway, because the programmer is much better specifying true to ResolveNetworkInterfaces and getting the ACTUAL network with which the device is contactable rather than all interfaces on the same subnet (which may not necessarily be the same network.

However, im open to suggestions so if you find a better way, please post it and I will use that instead! :D

Until I see something better though, these functions will only support LinkLocal and SiteLocal addresses (and maybe Global addresses with the second example), which will cover most setups for now.


I think given the specification requirements, it may be best to remove any checks regarding link local, site local (which we didn't discuss), global or multicast.

It should just check for subnet, simple. The devices will take care of the rest.

 

I have a 2 questions:

1. When will you release the updated frameowrk (I'm waiting to make the next release and want to make a fully functional release)

2. Is the updated framework providing support to automatically check the subnet/interface and enable UPnP or does one need to do that manually using hte API's for the subnet and then AddPortMapping?

3. I'm developing software to support multiple OS's from Win XP SP3 through Win 7. If I specify ResolveInterface = true, what behaviour can I expect (regarding the code we are discussing above) for the OS's? How would I need to change my code to handle WinXP vs Win7 (if any at all)

 

Thanks

Coordinator
Sep 24, 2012 at 3:00 AM

Yeah I think I will do that.

 

Answers :) 

1. Probably not for a few days, simply change the subnetBits to 64 instead of 56 and remove the link local check and you will basically have the new version, which I will release in a few days anyways

 

2. No, the UPnP framework is a generic framework for working with ALL UPnP devices, it will never have anything specific to a UPnP device, I added these new functions reluctantly when I realised that getting the IP address details (control point, or device) is something that one may want to do in many situations not just for port mapping, so I made an exception for these properties. You will still have to find the services and call AddPortMapping, but at least the framework can determine the IP address of the interface for you.

 

3. If you specify resolveInterfaces on Windows XP, it will not error, it just simply will not be available, so the InterfaceGuidAvailable property on the device will be false and the InterfaceGuid property will point to a 0 GUID. However, the new properties will automatically then switch over to using the subnet masking technique to determine the local IP address of the interfaces with which that connects to. Please see the main form_load event handler code in the ManagedUPnPTest application that comes with ManagedUPnP to see how its done.

Basically if its available it will work, if its not available (ie. Windows XP) then it simply will not determine the interface address, but the new properties on the device will fall back to legacy mode and still work.

Sep 24, 2012 at 3:54 AM

Okay thanks,one question on ipv6. how did you test it?

When I'm converting the Address toString to send to the AddPortMapping action, I notice the ipv6 address ends with a %x (where x is the interface identifier). I don't have any ipv6 devices to test so the question is do the ipv6 devices accept the %x at the end of the address or how did you handle it?

Sep 24, 2012 at 4:25 AM
thetoid wrote:

Yeah I think I will do that.

 

Answers :) 

1. Probably not for a few days, simply change the subnetBits to 64 instead of 56 and remove the link local check and you will basically have the new version, which I will release in a few days anyways

 

2. No, the UPnP framework is a generic framework for working with ALL UPnP devices, it will never have anything specific to a UPnP device, I added these new functions reluctantly when I realised that getting the IP address details (control point, or device) is something that one may want to do in many situations not just for port mapping, so I made an exception for these properties. You will still have to find the services and call AddPortMapping, but at least the framework can determine the IP address of the interface for you.

 

3. If you specify resolveInterfaces on Windows XP, it will not error, it just simply will not be available, so the InterfaceGuidAvailable property on the device will be false and the InterfaceGuid property will point to a 0 GUID. However, the new properties will automatically then switch over to using the subnet masking technique to determine the local IP address of the interfaces with which that connects to. Please see the main form_load event handler code in the ManagedUPnPTest application that comes with ManagedUPnP to see how its done.

Basically if its available it will work, if its not available (ie. Windows XP) then it simply will not determine the interface address, but the new properties on the device will fall back to legacy mode and still work.


One more thing I noticed, when enabling the Firewall it would be great to add 2 options:

1. Make dialogue popup optional

2. Pass an option to enable/disable the firewall through an option rather than a dialogue

3. Option to enable the firewall on windows Vista/7. For eg. I'm using this code through a Service with Admin access so the service can enable the firewall without user intervention

Coordinator
Sep 24, 2012 at 4:49 AM
Edited Sep 24, 2012 at 4:56 AM

I tested it from a Win 7 64 Machine and from a Windows XP machine with detecting the Windows Media Player device on another Win 7 64 machine and it works, but as for the port mappings with IPv6 I have no idea.

Funny thing is after all this, does your router even support mappings to IPv6 addresses? And if it does I would presume that you would NOT want the Scope or Interface identifier after it, as it is of no use to a mapping, so I would definitely remove it before using AddPortMapping, and thats IF your router supports mapping of IPv6 devices :). 

1 & 2. The dialogue popup can be made optional, but personally I dont like adding firewall rules without the users express permission, I might change this in a future version though.

3. The reason I did not bother with this is because the UPnP traffic was allowed by default for me on Windows 7 - actually I think the first time it comes up and asks you if you want to enable the rules for it (well it did on my isntallations anyway). This combined with the fact that it would require admin privelages which really is out of scope for a UPnP framework I decided not to bother. 

However, cheers for the ideas, I will look at adding that in a future version as well! :)

Coordinator
Sep 24, 2012 at 12:41 PM

In regards to not showing the dialog for the user, this can already be done, please check the help file out for the WindowsFirewall.UPnPPortsOpen property, you can also check the WindowsFirewall.CheckUPnPFirewallRules on how to do this without the Dialogs showing.