]> git.basschouten.com Git - openhab-addons.git/blob
dd89666e747a7833cff6351202317c72159b4b8f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.amazondashbutton.internal.capturing;
14
15 import java.util.concurrent.ExecutorService;
16 import java.util.concurrent.Executors;
17
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;
33
34 /**
35  * The {@link PacketCapturingService} is responsible for capturing packets.
36  *
37  * @author Oliver Libutzki - Initial contribution
38  *
39  */
40 public class PacketCapturingService {
41
42     private final Logger logger = LoggerFactory.getLogger(PacketCapturingService.class);
43
44     private static final int READ_TIMEOUT = 10; // [ms]
45     private static final int SNAPLEN = 65536; // [bytes]
46
47     private final PcapNetworkInterfaceWrapper pcapNetworkInterface;
48
49     private PcapHandle pcapHandle;
50
51     public PacketCapturingService(PcapNetworkInterfaceWrapper pcapNetworkInterface) {
52         this.pcapNetworkInterface = pcapNetworkInterface;
53     }
54
55     /**
56      * Calls {@link #startCapturing(PacketCapturingHandler, String)} with a null MAC address.
57      *
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
60      */
61     public boolean startCapturing(final PacketCapturingHandler packetCapturingHandler) {
62         return startCapturing(packetCapturingHandler, null);
63     }
64
65     /**
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.
69      *
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.
72      *
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
75      *            criteria
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
78      */
79
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.");
84             } else {
85                 pcapHandle.close();
86             }
87         }
88         try {
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);
93             }
94             pcapHandle.setFilter(filterBuilder.toString(), BpfCompileMode.OPTIMIZE);
95         } catch (Exception e) {
96             logger.error("Capturing packets on device {} failed.", pcapNetworkInterface.getName(), e);
97             return false;
98         }
99         ExecutorService executorService = Executors.newSingleThreadExecutor();
100         executorService.submit(() -> {
101             try {
102                 pcapHandle.loop(-1, new PacketListener() {
103
104                     @Override
105                     public void gotPacket(Packet packet) {
106                         if (!packet.contains(EthernetPacket.class)) {
107                             return;
108                         }
109                         final EthernetPacket ethernetPacket = packet.get(EthernetPacket.class);
110                         final MacAddress sourceMacAddress = ethernetPacket.getHeader().getSrcAddr();
111                         if (shouldCapture(packet)) {
112                             packetCapturingHandler.packetCaptured(sourceMacAddress);
113                         }
114                     }
115                 });
116             } finally {
117                 if (pcapHandle != null && pcapHandle.isOpen()) {
118                     pcapHandle.close();
119                     pcapHandle = null;
120                 }
121             }
122             return null;
123         });
124         if (macAddress == null) {
125             logger.debug("Started capturing ARP and BOOTP requests for network device {}.",
126                     pcapNetworkInterface.getName());
127         } else {
128             logger.debug("Started capturing ARP  and BOOTP requests for network device {} and MAC address {}.",
129                     pcapNetworkInterface.getName(), macAddress);
130         }
131         return true;
132     }
133
134     /**
135      * Checks if the given {@link Packet} should be captured.
136      *
137      * @param packet The packet to be checked
138      * @return Returns true, if the packet should be captured, otherwise false
139      */
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)) {
145                 return true;
146             }
147         }
148         if (packet.contains(UdpPacket.class)) {
149             final UdpPacket udpPacket = packet.get(UdpPacket.class);
150             if (UdpPort.BOOTPS == udpPacket.getHeader().getDstPort()) {
151                 return true;
152             }
153         }
154         return false;
155     }
156
157     /**
158      * Stops the capturing. This can be called without calling {@link #startCapturing(PacketCapturingHandler)} or
159      * {@link #startCapturing(PacketCapturingHandler, String)} before.
160      */
161     public void stopCapturing() {
162         if (pcapHandle != null) {
163             if (pcapHandle.isOpen()) {
164                 try {
165                     pcapHandle.breakLoop();
166                     logger.debug("Stopped capturing ARP and BOOTP requests for network device {}.",
167                             pcapNetworkInterface.getName());
168                 } catch (NotOpenException e) {
169                     // Just ignore
170                 }
171             } else {
172                 pcapHandle = null;
173             }
174         }
175     }
176
177     /**
178      * Returns the tracked {@link PcapNetworkInterfaceWrapper}.
179      *
180      * @return the tracked {@link PcapNetworkInterfaceWrapper}
181      */
182     public PcapNetworkInterfaceWrapper getPcapNetworkInterface() {
183         return pcapNetworkInterface;
184     }
185 }