Independent game developers - making Avoyd

LAN discovery with multiple adapters

Doug Binks - 05 Dec 2013


I was recently testing my LAN server browser dialogue menu with multiple machines running servers when I encountered an unusual issue. I had two machines, each running a server and a client. On one machine the client could see both servers, but on the other machine only the local server was found. The application had firewall permissions, and since the server was both sending and receiving packets without problem the firewall didn't seem to be the culprit.

Avoyd screenshot - user interface showing LAN discovery with two servers running on two different machines Working LAN discovery with two servers running on two different machines. Note the two IP addresses listed for Machine 1.

I've been writing TCP/IP networking code (specifically UDP for gaming) on and off for two decades and this was the first time I'd had issues on what appeared to be a relatively simple network topology. Things can get tough when you're in a company with routers and a large internal address space, but with all machines on one router this should have worked. So I was a bit surprised.

LAN network browsing is important for the case where players can't connect to the internet to communicate with a master server browser but do have a local connection, or simply want to run their own private game quickly.

The penny dropped when I noticed that the client on the problem machine was finding the local server on a different address to that the other machines were showing. This wasn't the loop-back (127.0.0.1) address, but another IP in an internal only range. Checking my network adapters I noticed I had an ethernet adapter which had been installed by VirtualBox. Although I didn't have any machines running, the client was somehow performing network discovery via that adapter. It appears that the Windows TCP/IP stack doesn't propagate the 255.255.255.255 broadcast to all adapters, so the broadcast ping was only local.

The solution was to enumerate all of the host IP addresses, and in addition to broadcasting a ping to 255.255.255.255 I also send a ping to each address with the last byte replaced with 255. With most small networks having a subnet mask of 255.255.255.0 this should work well. This will result in multiple servers being found if they're on a the same machine as the client and there is more than one ethernet adapter, but is unlikely except during development and also doesn't pose a problem to finding a server.

A final solution would be to ping every IP address on your subnet in case of an unusual subnet mask, but a better approach might be to have servers display their IP and clients able to input it manually as this should cover all bases.

To find out what network addresses your system has, you need to call gethostname to get the name of the current machine, and then call gethostbyname (note the 'by' here) to go from the host to a structure containing a list of address or you can use the getaddrinfo function. The socket will need to have broadcast permissions set with setsockopt. I've used links to the Windows documentation here as that's the OS I discovered the issue on. The issue may also exist on other systems but I've not as yet tested this. The solution shouldn't cause any problems, so I'm using it across all platforms.

If like me you're using Raknet, the code would look something like this:

//ping the server
const unsigned int NETWORK_PORT_SERVER = 23947;
pRakPeerInterface->Ping( "255.255.255.255", NETWORK_PORT_SERVER, true );

//also ping each IP address at X.Y.Z.255 in case of multiple adapters
unsigned int numLocalAddresses = pRakPeerInterface->GetNumberOfAddresses( );
for( unsigned int address = 0; address < numLocalAddresses; ++address )
{
    std::string strAddress = pRakPeerInterface->GetLocalIP( address );
    size_t idx = strAddress.find_last_of( '.' );
    if( idx < strAddress.length() )
    {
        std::string pingAddress = strAddress.substr( 0, idx ) + ".255";
        pRakPeerInterface->Ping( pingAddress.c_str(), NETWORK_PORT_SERVER, true );
    }
}

To confirm this wasn't Raknet related I've replicated the issue with my own (less sophisticated) networking layer from the original Avoyd, and the solution works there too.

If you've got any questions on the implementation details, or you'd simply like to know more about networking let me know in the comments below. Meanwhile happy LAN discovery, free from multiple adapter confusion!

 › 2017
 › Avoyd Editor Prototype
 › 2016
 › Black triangles and Peter Highspot
 › Colour palettes and lighting
 › Concept art by Rebecca Michalak
 › 2015
 › Internals of a lightweight task scheduler
 › Implementing a lightweight task scheduler
 › Feral Vector
 › Normal generation in the pixel shader
 › 2014
 › Python Google App Engine debugging with PyCharm CE
 › Lighting voxel octrees and procedural texturing
 › Patterns and spheres
 › Python Google App Engine debugging with PyTools
 › Interview
 › Domain masking using Google App Engine
 › Octree streaming - part 4
 › Black triangles and nervous_testpilot
 › Presskit for Google App Engine
 › Octree streaming - part 3
 › Octree streaming - part 2
 › Octree streaming
 › 2013
 ›› LAN discovery with multiple adapters 
 › Playing with material worlds
 › Developer Diary archive
 › Website redesign
 › First Person Editor
 › First Avoyd tech update video
 › Implementing a static website in Google App Engine
 › Multiplayer editing
 › First screenshots
 › Thoughts on gameplay modes
 › Back in 1999
 › 2002
 › ECTS 2002
 › Avoyd Version 1.6.1 out
 › Avoyd Version 1.6 out
 › 2001
 › Biting the bullet
 › Avoyd version 1.5 out
 › Monday Mayhem
 › Avoyd version 1.5 alpha 1 out
 › Avoyd version 1.4 out
 › ECTS 2001
 › Fun with Greek letters
 › Closer just a little closer
 › Back already
 › Artificial Humanity
 › Products and promises
 › Ecommerce
 › Explosions galore
 › Spring fixes
 › Open source and ports to other operating systems
 › Avoyd LAN Demo Version 1.1 is out
 › Thanks for the support
 › Avoyd LAN Demo Ready
 › Game Tech
 › Internals of a lightweight task scheduler
 › Implementing a lightweight task scheduler
 › Normal generation in the pixel shader
 › Lighting voxel octrees and procedural texturing
 › Octree streaming - part 4
 › Octree streaming - part 3
 › Octree streaming - part 2
 › Octree streaming
 ›› LAN discovery with multiple adapters 
 › enkiTS
 › Internals of a lightweight task scheduler
 › Implementing a lightweight task scheduler
 › Web Tech
 › Python Google App Engine debugging with PyCharm CE
 › Python Google App Engine debugging with PyTools
 › Domain masking using Google App Engine
 › Presskit for Google App Engine
 › Implementing a static website in Google App Engine
 › Avoyd
 › Avoyd Editor Prototype
 › Black triangles and Peter Highspot
 › Colour palettes and lighting
 › Concept art by Rebecca Michalak
 › Feral Vector
 › Patterns and spheres
 › Interview
 › Black triangles and nervous_testpilot
 › Playing with material worlds
 › Website redesign
 › First Person Editor
 › First Avoyd tech update video
 › Multiplayer editing
 › First screenshots
 › Thoughts on gameplay modes
 › Back in 1999
 › Avoyd 1999
 › Developer Diary archive
 › Back in 1999
 › ECTS 2002
 › Avoyd Version 1.6.1 out
 › Avoyd Version 1.6 out
 › Biting the bullet
 › Avoyd version 1.5 out
 › Monday Mayhem
 › Avoyd version 1.5 alpha 1 out
 › Avoyd version 1.4 out
 › ECTS 2001
 › Fun with Greek letters
 › Closer just a little closer
 › Back already
 › Artificial Humanity
 › Products and promises
 › Ecommerce
 › Explosions galore
 › Spring fixes
 › Open source and ports to other operating systems
 › Avoyd LAN Demo Version 1.1 is out
 › Thanks for the support
 › Avoyd LAN Demo Ready