2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.amazondashbutton.internal.capturing;
15 import java.util.concurrent.ExecutorService;
16 import java.util.concurrent.Executors;
18 import org.openhab.binding.amazondashbutton.internal.pcap.PcapNetworkInterfaceWrapper;
19 import org.pcap4j.core.BpfProgram.BpfCompileMode;
20 import org.pcap4j.core.NotOpenException;
21 import org.pcap4j.core.PacketListener;
22 import org.pcap4j.core.PcapHandle;
23 import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode;
24 import org.pcap4j.packet.ArpPacket;
25 import org.pcap4j.packet.EthernetPacket;
26 import org.pcap4j.packet.Packet;
27 import org.pcap4j.packet.UdpPacket;
28 import org.pcap4j.packet.namednumber.ArpOperation;
29 import org.pcap4j.packet.namednumber.UdpPort;
30 import org.pcap4j.util.MacAddress;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
35 * The {@link PacketCapturingService} is responsible for capturing packets.
37 * @author Oliver Libutzki - Initial contribution
40 public class PacketCapturingService {
42 private final Logger logger = LoggerFactory.getLogger(PacketCapturingService.class);
44 private static final int READ_TIMEOUT = 10; // [ms]
45 private static final int SNAPLEN = 65536; // [bytes]
47 private final PcapNetworkInterfaceWrapper pcapNetworkInterface;
49 private PcapHandle pcapHandle;
51 public PacketCapturingService(PcapNetworkInterfaceWrapper pcapNetworkInterface) {
52 this.pcapNetworkInterface = pcapNetworkInterface;
56 * Calls {@link #startCapturing(PacketCapturingHandler, String)} with a null MAC address.
58 * @param packetCapturingHandler The handler to be called every time packet is captured
59 * @return Returns true, if the capturing has been started successfully, otherwise returns false
61 public boolean startCapturing(final PacketCapturingHandler packetCapturingHandler) {
62 return startCapturing(packetCapturingHandler, null);
66 * Starts the capturing in a dedicated thread, so this method returns immediately. Every time a packet is captured,
67 * the {@link PacketCapturingHandler#packetCaptured(MacAddress)} of the given
68 * {@link PacketCapturingHandler} is called.
70 * It's possible to capture packets sent by a specific MAC address by providing the given parameter. If the
71 * macAddress is null, all MAC addresses are considered.
73 * @param packetCapturingHandler The handler to be called every time a packet is captured
74 * @param macAddress The source MAC address of the captured packet, might be null in order to deactivate this filter
76 * @return Returns true, if the capturing has been started successfully, otherwise returns false
77 * @throws IllegalStateException Thrown if {@link PcapHandle#isOpen()} of {@link #pcapHandle} returns true
80 public boolean startCapturing(final PacketCapturingHandler packetCapturingHandler, final String macAddress) {
81 if (pcapHandle != null) {
82 if (pcapHandle.isOpen()) {
83 throw new IllegalStateException("There is an open pcap handle.");
89 pcapHandle = pcapNetworkInterface.openLive(SNAPLEN, PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
90 StringBuilder filterBuilder = new StringBuilder("(arp or port bootps)");
91 if (macAddress != null) {
92 filterBuilder.append(" and ether src " + macAddress);
94 pcapHandle.setFilter(filterBuilder.toString(), BpfCompileMode.OPTIMIZE);
95 } catch (Exception e) {
96 logger.error("Capturing packets on device {} failed.", pcapNetworkInterface.getName(), e);
99 ExecutorService executorService = Executors.newSingleThreadExecutor();
100 executorService.submit(() -> {
102 pcapHandle.loop(-1, new PacketListener() {
105 public void gotPacket(Packet packet) {
106 if (!packet.contains(EthernetPacket.class)) {
109 final EthernetPacket ethernetPacket = packet.get(EthernetPacket.class);
110 final MacAddress sourceMacAddress = ethernetPacket.getHeader().getSrcAddr();
111 if (shouldCapture(packet)) {
112 packetCapturingHandler.packetCaptured(sourceMacAddress);
117 if (pcapHandle != null && pcapHandle.isOpen()) {
124 if (macAddress == null) {
125 logger.debug("Started capturing ARP and BOOTP requests for network device {}.",
126 pcapNetworkInterface.getName());
128 logger.debug("Started capturing ARP and BOOTP requests for network device {} and MAC address {}.",
129 pcapNetworkInterface.getName(), macAddress);
135 * Checks if the given {@link Packet} should be captured.
137 * @param packet The packet to be checked
138 * @return Returns true, if the packet should be captured, otherwise false
140 @SuppressWarnings("PMD.CompareObjectsWithEquals")
141 private boolean shouldCapture(final Packet packet) {
142 if (packet.contains(ArpPacket.class)) {
143 ArpPacket arpPacket = packet.get(ArpPacket.class);
144 if (arpPacket.getHeader().getOperation().equals(ArpOperation.REQUEST)) {
148 if (packet.contains(UdpPacket.class)) {
149 final UdpPacket udpPacket = packet.get(UdpPacket.class);
150 if (UdpPort.BOOTPS == udpPacket.getHeader().getDstPort()) {
158 * Stops the capturing. This can be called without calling {@link #startCapturing(PacketCapturingHandler)} or
159 * {@link #startCapturing(PacketCapturingHandler, String)} before.
161 public void stopCapturing() {
162 if (pcapHandle != null) {
163 if (pcapHandle.isOpen()) {
165 pcapHandle.breakLoop();
166 logger.debug("Stopped capturing ARP and BOOTP requests for network device {}.",
167 pcapNetworkInterface.getName());
168 } catch (NotOpenException e) {
178 * Returns the tracked {@link PcapNetworkInterfaceWrapper}.
180 * @return the tracked {@link PcapNetworkInterfaceWrapper}
182 public PcapNetworkInterfaceWrapper getPcapNetworkInterface() {
183 return pcapNetworkInterface;