All posts

Link Layer Discovery Protocol: A Weekend Project Journal

The Link Layer Discovery Protocol (LLDP) is a networking protocol that allows network devices to advertise information about themselves to other devices on the network. This information includes device name, port ID,…

What is Link Layer Discovery Protocol (LLDP)?

The Link Layer Discovery Protocol (LLDP) is a networking protocol that allows network devices to advertise information about themselves to other devices on the network. This information includes device name, port ID, and capabilities. LLDP can be used to discover neighboring devices and their characteristics to troubleshoot networks and manage devices. It is a vendor-neutral and standardized protocol, meaning it can be implemented and used on devices from different manufacturers. In an enterprise environment trying to find what port is patched into your switch can be a pain, LLDP makes it incredibly easy to find that information.

A lot of my past projects have focused on building things in Powershell and trying to push the limits of what we can do in the environment this project was supposed to be another Powershell one but early on I learned that gaining access to the networking levels I needed to would be impossible using PowerShell cmdlets or C# out of the box but I didn’t let that stop me. In my research, I found a .NET wrapper for WinPcap/Npcap called Pcap.net and that fueled my inspiration for my little weekend project.

Npcap is built upon WinPcap and is a packet capture library that allows us to not just analyze incoming and going packets but also allows us to build custom packets and implement protocols that may not be supported natively on for example Windows. Written in C#, I used Pcap.net to build a command line application that allows us to choose an interface to broadcast or listen to LLDP frames. This article will solely cover the listening aspect of the program and in the future, I will dive into the broadcasting portion.

For this project, I didn’t want to reinvent the wheel so I used a lot of the ideas present in the Pcap.net tutorials, specifically the packet capture section, and modified it to make it easier to read. Here’s an example of the main function:

static void Main()
    {

        IList<LivePacketDevice> allDevices = LivePacketDevice.AllLocalMachine;

        if (allDevices.Count == 0)
        {
            Console.WriteLine("No interfaces found! Make sure WinPcap/NpCap is installed.");
            return;
        }

        // Print the list
        for (int i = 0; i != allDevices.Count; ++i)
        {
            LivePacketDevice device = allDevices[i];
            Console.WriteLine((i + 1) + ". " + device.Description);
        }

        int deviceIndex = 0;

        do
        {
            Console.WriteLine("Enter the interface number (1-" + allDevices.Count + "):");
            string deviceIndexString = Console.ReadLine();
            if (!int.TryParse(deviceIndexString, out deviceIndex) ||
                deviceIndex < 1 || deviceIndex > allDevices.Count)
            {
                deviceIndex = 0;
            }
        } while (deviceIndex == 0);

        // Take the selected adapter
        PacketDevice selectedDevice = allDevices[deviceIndex - 1];

        Console.WriteLine("Send(0) or Receive(1) LLDP Frames? ");
        string SendOrReceive = Console.ReadLine();

        if (SendOrReceive == "0")
        {
            BuildLLDPPacket(selectedDevice);
        }
        else if (SendOrReceive == "1")
        {
            GetLLDPPacket(selectedDevice);
        }
        else 
        {
            Console.WriteLine(" Invalid Option Selected");
            return;
        }
        
    }

The main function isn’t doing anything special, we’re instantiating a LivePacketDevice object and using it to get a list of all network interfaces/devices available to us. We can access all these devices through the allDevices variable but in our case, we only want to care about the ethernet interface. All devices will still be shown and be available as an option. We’ll then prompt the user for the interface we want to listen on and then assign that to the variable selectedDevice.

We’ll now want to modify the Packet Handler function and create our function that uses the PacketHandler to begin capturing the packets.

Let’s start with the GetLLDPPacket function, which is the same as the one located here but with a modified BerkeleyPacketFilter that only filters for ethernet multicast messages. This is helpful as we have now reduced the packets we can see to ARP and any other Layer 2 frames/packets like LLDP, CDP, etc.

public static void GetLLDPPacket(PacketDevice Interface) 
    {
        using (PacketCommunicator communicator =
                Interface.Open(65536,PacketDeviceOpenAttributes.Promiscuous,1000))
        {
            // Check the link layer. We support only Ethernet for simplicity.
            if (communicator.DataLink.Kind != DataLinkKind.Ethernet)
            {
                Console.WriteLine("This program works only on Ethernet networks.");
                return;
            }

            // Compile the filter
            using (BerkeleyPacketFilter filter = communicator.CreateFilter("ether multicast"))
            {
                // Set the filter
                communicator.SetFilter(filter);
            }

            Console.WriteLine("Listening on " + Interface.Description + "...");

            // start the capture
            communicator.ReceivePackets(0, PacketHandler);
        }
    }

Lastly, we have our packet handler and this is where a lot of the heavy modifications occur. Pcap.net doesn’t support the parsing of LLDP frames so we will have to parse through and interpret every byte of data. This article isn’t so much a tutorial as it is a journal of my thought process but I will include a small section of how I decided to parse through the captured data.

private static void PacketHandler(Packet packet)
{   

    if (packet.Ethernet.EtherType.ToString() == "35020")
    {
        Console.WriteLine(packet.Timestamp.ToString("yyyy-MM-dd hh:mm:ss.fff") + " length:" + packet.Length);
        List<string> hexArray = new List<string>();
        var hexString = packet.Ethernet.ToHexadecimalString();
        for (int i = 0; i < packet.Length; i+=2) 
        {
            hexArray.Add(hexString.Substring(i, 2));
        }
        Console.WriteLine("Destination: " + string.Join(":",hexArray.GetRange(0,6)));
        Console.WriteLine("Source: " + string.Join(":", hexArray.GetRange(6, 6)));
        Console.WriteLine("Type: " + string.Join("", hexArray.GetRange(12, 2)));
        System.Environment.Exit(1);
    }
}

I first took the raw ethernet packet converted it to a hexadecimal string and used a for loop to create a “byte list” which allows me to iterate through every byte and use the range function to get a range of values from the string list.

The first 6 bytes contain the Destination MAC Address, the next 6 include the Source MAC Address and the following 2 bytes includes the Ethernet Type which in our case should always be 0x88cc or just 88cc which is the Ethernet Type for LLDP. With LLDP things become slightly more complicated. If you look at the below image from Cisco that breaks down the frame format, you will notice that the following TLVs don’t use the bytes equally meaning that of the first byte or 8 bits, only 7 of them refer to the type, the remaining 9 bits are used for the length of the data.

https://learningnetwork.cisco.com/s/article/link-layer-discovery-protocol-lldp-x

This is just a glimpse into my weekend project and I hope it inspires others to pick up a topic and have fun with it in their downtime.

Discussion

No comments yet