Search code examples
c#httpmonobroadcast

How to broadcast HTTP request?


I'm designing a data acquisition system with sensor nodes that communicate with a server using HTTP, in Mono/C#. I'm using basic HttpWebRequest and HttpListener to implement the communication.

The server will have an app with a Search button to find and display nodes present in LAN. Basically the nodes will listen for a specific Hello message (HTTP request) received on a specific port and respond with their ID when they receive it.

My questions are: how can the HTTP request be broadcasted to all nodes in the network? Alternatively, how can I get the IP addresses of all machines connected in LAN to send the request to each of them?

If it is a simpler way to implement this, I am open to new suggestions. Thanks you!


Solution

  • As stated in the comments, HTTP cannot be broadcast like this.

    Enumerating all the IP addresses in your IP range and sending an HTTP request to each one to see if there's something there is not a very good idea. How much of a not-a-very-good-idea depends on which of the Private IP Address Range and subnet mask your DHCP server gives out

    • The most common configuration uses the 192.168..* range with a 255.255.255.0 mask (also known as 192.168.0.0/24), and only gives 255 addresses.
    • If your LAN uses a 192.168..* range (a mask of 255.255.0.0), this code will give you 65'535 IP addresses for you to send your HTTP requests to,
    • If your LAN uses a 172...* address, you will get 1'048'575 IP addresses, and
    • If your LAN uses a 10...* address range, you will have an impressive 16'777'215 addresses to search.

    That's potentially a lot of IP addresses you have to search.

    If your IP range is anything other than 192.168.0.0/24, then you also won't be able to probe all of those at the same time. Even if you don't mind spawning 65 thousand threads, your machine doesn't have enough TCP ports that it can use for the responses (depending on the OS, you might get maybe 30'000) so you'll have to go through them in batches.

    Furthermore, if your network is even slightly complicated, using vLANs or routers to separate different areas of the network, then your machine will not be able to enumerate the ranges used by those areas by itself. At that point you're likely finding a way to query the router or ActiveDirectory to find the IP ranges or hosts outside of your network segment.

    All in all, it's not such a good idea.

    UDP's Probably Better

    A better approach is to use (as others have suggested) a UDP broadcast. Each of your sensor nodes would listen on a specific UDP port, and your server will send one UDP message to the broadcast IP address for your subnet. Each node will receive the message, and your code at the node will then send some form of response back to the source of the UDP broadcast (the server). The server will then receive the UDP response from each node from which includes the IP address of each of them.

    At the code level, you create a socket, configure it for UDP with a chosen port number, and your server starts receiving data on that port using your paradigm of choice (synchronous, begin/end, async/await). When data arrives at the port, your callback function is fired, and is passed the data that was received, and the IPEndPoint of the service that sent it.

    The different routers in your network configuration can typically be set to forward your UDP broadcast requests and the associated responses so that with minimal configuration (no more than you'd need to do to get the HTTP requests to work anyway) you can search outside your network segment.

    Examples of a simple UDP server in C# can be found here.

    Finding the Subnets

    Whichever way you choose to do it, getting all the subnets with their broadcast addresses or complete set of IP addresses can be done with the following code. It will find all the IP addresses on all the subnets on all the adapters (in all the gin-joints in all the world) on your machine.

    This code doesn't eliminate the 127...* local addresses, which you probably want to do to avoid having another 16 million addresses to pointlessly search.

    foreach ( var lSubnet in GetLocalSubnets() )
    {
        var lBroadcast = lSubnet.subnetBroadastAddress;
        var lAddresses = new List<IPAddress>( lSubnet.GetAllAddresses() );
    }
    
    public static IEnumerable<IpAddressSubnet> GetLocalSubnets()
    {
        foreach (NetworkInterface lAdapter in NetworkInterface.GetAllNetworkInterfaces())
        {
            foreach (UnicastIPAddressInformation lAdapterIpAddress in lAdapter.GetIPProperties().UnicastAddresses)
            {
                if (lAdapterIpAddress.Address.AddressFamily == AddressFamily.InterNetwork)
                {
                    yield return new IpAddressSubnet(lAdapterIpAddress.Address, lAdapterIpAddress.IPv4Mask);
                }
            }
        }
        yield break;
    }
    
    public class IpAddressSubnet
    {
        public IpAddressSubnet(IPAddress pAddress, IPAddress pSubnetMask)
        {
            address = pAddress;
            subnetMask = pSubnetMask;
    
            var lAddressBytes = pAddress.GetAddressBytes();
            var lSubmaskBytes = pSubnetMask.GetAddressBytes();
            var lSubmaskInverted = lSubmaskBytes.Select((b) => (byte)(b ^ 255)).ToArray();
    
            var lSubnetBaseAddressBytes = lAddressBytes.Zip(lSubmaskBytes, (a, m) => (byte)(a & m)).ToArray();
            subnetBaseAddress = new IPAddress(lSubnetBaseAddressBytes);
            subnetBaseAddressUint = BitConverter.ToUInt32( lSubnetBaseAddressBytes.Reverse().ToArray(), 0 );
    
            subnetBroadastAddress = new IPAddress(lAddressBytes.Zip(lSubmaskInverted, (a, m) => (byte)(a | m)).ToArray());
            subnetSize = BitConverter.ToUInt32( lSubmaskInverted.Reverse().ToArray(), 0 );
        }
    
        public IPAddress address { get; set; }
        public IPAddress subnetMask { get; set; }
        public IPAddress subnetBaseAddress { get; set; }
        uint subnetBaseAddressUint { get; set; }
        public IPAddress subnetBroadastAddress { get; set; }
        public uint subnetSize { get; set; }
    
        public IEnumerable<IPAddress> GetAllAddresses()
        {
            for ( uint i = 0 ; i < subnetSize - 1 ; ++ i )  // Remove 1 for the broadcast address
            {
                uint lIp = subnetBaseAddressUint + i;
                yield return new IPAddress( BitConverter.GetBytes(lIp).Reverse().ToArray() );
            }
            yield break;
        }
    }
    

    Hope this helps