This is an automated email from the ASF dual-hosted git repository. hutcheb pushed a commit to branch plc4j/profinet in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 83becf0591431f76d0b34d3ede14fd4f64db83b2 Author: Ben Hutcheson <[email protected]> AuthorDate: Thu Sep 8 12:49:59 2022 -0600 fix(plc4j(profinet): Started to add the LLDP broadcast --- .../profinet/discovery/ProfinetPlcDiscoverer.java | 102 +++++++++++++++++++++ .../resources/protocols/profinet/profinet.mspec | 22 +++++ 2 files changed, 124 insertions(+) diff --git a/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/discovery/ProfinetPlcDiscoverer.java b/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/discovery/ProfinetPlcDiscoverer.java index 5eb899bcc..96b2dba67 100644 --- a/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/discovery/ProfinetPlcDiscoverer.java +++ b/plc4j/drivers/profinet/src/main/java/org/apache/plc4x/java/profinet/discovery/ProfinetPlcDiscoverer.java @@ -51,6 +51,7 @@ import java.util.concurrent.Executors; public class ProfinetPlcDiscoverer implements PlcDiscoverer { private static final EtherType PN_EtherType = EtherType.getInstance((short) 0x8892); + private static final EtherType LLDP_EtherType = EtherType.getInstance((short) 0x88cc); // The constants for the different block names and their actual meaning. private static final String DEVICE_TYPE_NAME = "DEVICE_PROPERTIES_OPTION-1"; @@ -279,6 +280,107 @@ public class ProfinetPlcDiscoverer implements PlcDiscoverer { return future; } + public void lldpProbe() { + Set<PcapHandle> openHandles = new HashSet<>(); + try { + for (PcapNetworkInterface dev : Pcaps.findAllDevs()) { + // It turned out on some MAC network devices without any ip addresses + // the compiling of the filter expression was causing errors. As + // currently there was no other way to detect this, this check seems + // to be sufficient. + if(dev.getAddresses().size() == 0) { + continue; + } + if (!dev.isLoopBack()) { + for (LinkLayerAddress linkLayerAddress : dev.getLinkLayerAddresses()) { + org.pcap4j.util.MacAddress macAddress = (org.pcap4j.util.MacAddress) linkLayerAddress; + PcapHandle handle = dev.openLive(65536, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, 10); + openHandles.add(handle); + + ExecutorService pool = Executors.newSingleThreadExecutor(); + + // Only react on PROFINET DCP packets targeted at our current MAC address. + handle.setFilter( + "(ether proto 0x88cc)", + BpfProgram.BpfCompileMode.OPTIMIZE); + + PacketListener listener = + packet -> { + // EthernetPacket is the highest level of abstraction we can be expecting. + // Everything inside this we will have to decode ourselves. + if (packet instanceof EthernetPacket) { + EthernetPacket ethernetPacket = (EthernetPacket) packet; + boolean isLldpPacket = false; + // I have observed sometimes the ethernet packets being wrapped inside a VLAN + // Packet, in this case we simply unpack the content. + if (ethernetPacket.getPayload() instanceof Dot1qVlanTagPacket) { + Dot1qVlanTagPacket vlanPacket = (Dot1qVlanTagPacket) ethernetPacket.getPayload(); + if (LLDP_EtherType.equals(vlanPacket.getHeader().getType())) { + isLldpPacket = true; + } + } else if (LLDP_EtherType.equals(ethernetPacket.getHeader().getType())) { + isLldpPacket = true; + } + + // It's a LLDP packet. + if (isLldpPacket) { + ReadBuffer reader = new ReadBufferByteBased(ethernetPacket.getRawData()); + try { + Ethernet_Frame ethernetFrame = Ethernet_Frame.staticParse(reader); + PnDcp_Pdu pdu; + // Access the pdu data (either directly or by + // unpacking the content of the VLAN packet. + if (ethernetFrame.getPayload() instanceof Ethernet_FramePayload_VirtualLan) { + Ethernet_FramePayload_VirtualLan vlefpl = (Ethernet_FramePayload_VirtualLan) ethernetFrame.getPayload(); + pdu = ((Ethernet_FramePayload_PnDcp) vlefpl.getPayload()).getPdu(); + } else { + pdu = ((Ethernet_FramePayload_PnDcp) ethernetFrame.getPayload()).getPdu(); + } + // Inspect the PDU itself + // (in this case we only process identify response packets) + + + logger.debug("Found new device: '{}' using LLDP '{}'", + "Not Sure", "Not Sure"); + } catch (ParseException ex) { + throw new RuntimeException(ex); + } + } + } + }; + Task t = new Task(handle, listener); + pool.execute(t); + + // Construct and send the LLDP Probe + + Ethernet_Frame identificationRequest = new Ethernet_Frame( + // Pre-Defined PROFINET discovery MAC address + new MacAddress(new byte[]{0x01, 0x0E, (byte) 0xCF, 0x00, 0x00, 0x00}), + toPlc4xMacAddress(macAddress), + new Ethernet_FramePayload_VirtualLan(VirtualLanPriority.BEST_EFFORT, false, 0, + new Ethernet_FramePayload_PnDcp( + new PnDcp_Pdu_IdentifyReq(PnDcp_FrameId.DCP_Identify_ReqPDU.getValue(), + 1, + 256, + Collections.singletonList( + new PnDcp_Block_ALLSelector() + ))))); + WriteBufferByteBased buffer = new WriteBufferByteBased(34); + identificationRequest.serialize(buffer); + Packet packet = EthernetPacket.newPacket(buffer.getData(), 0, 34); + handle.sendPacket(packet); + } + } + } + } catch (IllegalRawDataException | NotOpenException | PcapNativeException | SerializationException e) { + logger.error("Got an exception while processing raw socket data", e); + + for (PcapHandle openHandle : openHandles) { + openHandle.close(); + } + } + } + private static MacAddress toPlc4xMacAddress(org.pcap4j.util.MacAddress pcap4jMacAddress) { byte[] address = pcap4jMacAddress.getAddress(); return new MacAddress(new byte[]{address[0], address[1], address[2], address[3], address[4], address[5]}); diff --git a/protocols/profinet/src/main/resources/protocols/profinet/profinet.mspec b/protocols/profinet/src/main/resources/protocols/profinet/profinet.mspec index 09bfea4a5..b3c0d9ca0 100644 --- a/protocols/profinet/src/main/resources/protocols/profinet/profinet.mspec +++ b/protocols/profinet/src/main/resources/protocols/profinet/profinet.mspec @@ -82,9 +82,31 @@ ['0x8892' Ethernet_FramePayload_PnDcp [simple PnDcp_Pdu pdu ] ] + ['0x88cc' Ethernet_FramePayload_LLDP + [simple Lldp_Pdu pdu ] + ] ] ] +[type Lldp_Pdu + [simple TlvType_Pdu tlvChassisId] + [simple TlvType_Pdu tlvPortId] + [simple TlvTimeToLive tlvTimeToLive] +] + +[type TlvType_Pdu + [simple uint 7 tlvId ] + [implicit uint 9 tlvIdLength 'lengthInBytes' ] + [simple typeSubType idSubType ] + [simple vstring '(tlvIdLength * 8) + 1' Id ] +] + +[type TlvTimeToLive + [simple uint 7 tlvId ] + [implicit uint 9 tlvIdLength 'lengthInBytes' ] + [simple uint 16 tlvTimeToLive] +] + // 4.10.3.2 // A lot of the fields are set to constant values, which would // usually be dynamic. However are we trying to limit the number of
