You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2018/01/25 10:41:09 UTC
[incubator-plc4x] branch feature/PLC4X-18--raw-sockets updated: -
First implementation of a somewhat functional and usable RawSocket
implementation.
This is an automated email from the ASF dual-hosted git repository.
cdutz pushed a commit to branch feature/PLC4X-18--raw-sockets
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git
The following commit(s) were added to refs/heads/feature/PLC4X-18--raw-sockets by this push:
new 0c25c59 - First implementation of a somewhat functional and usable RawSocket implementation.
0c25c59 is described below
commit 0c25c59fa4120b7a26e09e0c1d032488dd60b071
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Thu Jan 25 11:41:04 2018 +0100
- First implementation of a somewhat functional and usable RawSocket implementation.
---
.../java/utils/rawsockets/netty/RawSocket.java | 268 +++++++++++++++++++++
.../utils/rawsockets/netty/RawSocketException.java | 16 ++
.../utils/rawsockets/netty/RawSocketListener.java | 7 +
.../java/utils/rawsockets/netty/RawSocketTest.java | 44 ++++
4 files changed, 335 insertions(+)
diff --git a/plc4j/utils/raw-sockets/src/main/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocket.java b/plc4j/utils/raw-sockets/src/main/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocket.java
new file mode 100644
index 0000000..4367fc9
--- /dev/null
+++ b/plc4j/utils/raw-sockets/src/main/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocket.java
@@ -0,0 +1,268 @@
+package org.apache.plc4x.java.utils.rawsockets.netty;
+
+import org.pcap4j.core.*;
+import org.pcap4j.packet.*;
+import org.pcap4j.packet.namednumber.*;
+import org.pcap4j.util.ByteArrays;
+import org.pcap4j.util.MacAddress;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.*;
+import java.util.concurrent.*;
+
+public class RawSocket {
+
+ private static final int SNAPLEN = 65536;
+ private static final int READ_TIMEOUT = 10;
+
+ private static final Map<InetAddress, MacAddress> arpCache = new HashMap<>();
+
+ // The id of the protocol we will be communicating in.
+ private final int protocolNumber;
+
+ private PcapNetworkInterface nif;
+ private InetAddress remoteIpAddress;
+ private MacAddress remoteMacAddress;
+ private InetAddress localIpAddress;
+ private MacAddress localMacAddress;
+ private ExecutorService pool = Executors.newSingleThreadExecutor();
+ private PcapHandle receiveHandle;
+
+ private final List<RawSocketListener> listeners = new LinkedList<>();
+
+ public RawSocket(int protocolNumber) {
+ this.protocolNumber = protocolNumber;
+ }
+
+ public void connect(String remoteAddress) throws RawSocketException {
+ try {
+ pool = Executors.newScheduledThreadPool(2);
+
+ remoteIpAddress = InetAddress.getByName(remoteAddress);
+
+ localIpAddress = getLocalNetworkAddress(remoteIpAddress);
+ if (localIpAddress == null) {
+ throw new RawSocketException("Unable to connect to the remote address " + remoteAddress);
+ }
+ nif = Pcaps.getDevByAddress(localIpAddress);
+ localMacAddress = MacAddress.getByAddress(nif.getLinkLayerAddresses().get(0).getAddress());
+
+ remoteMacAddress = getMacAddress(remoteIpAddress);
+
+ // Setup receiving of packets and redirecting them to the corresponding listeners.
+ // Filter packets to contain only the ip protocol number of the current protocol.
+ receiveHandle = nif.openLive(SNAPLEN, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
+ // Set the filter.
+ String filterString = "ip protochain " + protocolNumber +
+ " and ether dst " + localMacAddress.toString() +
+ " and ether src " + remoteMacAddress.toString();
+ receiveHandle.setFilter(filterString, BpfProgram.BpfCompileMode.OPTIMIZE);
+ PacketListener packetListener
+ = packet -> {
+ for(RawSocketListener listener : listeners) {
+ listener.packetReceived(packet.getRawData());
+ }
+ };
+ pool.execute(() -> {
+ try {
+ receiveHandle.loop(-1, packetListener);
+ } catch (PcapNativeException | InterruptedException | NotOpenException e) {
+ // TODO: This is not nice ... fix this ...
+ throw new RuntimeException("Error receiving ARP lookup", e);
+ }
+ });
+ } catch (PcapNativeException | NotOpenException | UnknownHostException e) {
+ throw new RawSocketException("Error setting up RawSocket", e);
+ }
+ }
+
+ public void disconnect() throws RawSocketException {
+ // TODO: Terminate all the listeners and the thread pool.
+ }
+
+ public void write(byte[] rawData) throws RawSocketException {
+ PcapHandle sendHandle = null;
+ try {
+ sendHandle = nif.openLive(SNAPLEN, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
+ UnknownPacket.Builder packetBuilder = new UnknownPacket.Builder();
+ packetBuilder.rawData(rawData);
+
+ IpV4Packet.Builder ipV4Builder = new IpV4Packet.Builder();
+ ipV4Builder
+ .version(IpVersion.IPV4)
+ .tos(IpV4Rfc791Tos.newInstance((byte) 0))
+ .ttl((byte) 100)
+ .protocol(new IpNumber((byte) protocolNumber, "plc4x"))
+ .srcAddr((Inet4Address) localIpAddress)
+ .dstAddr((Inet4Address) remoteIpAddress)
+ .payloadBuilder(packetBuilder)
+ .correctChecksumAtBuild(true)
+ .correctLengthAtBuild(true);
+ ipV4Builder.identification((short) 1);
+
+ EthernetPacket.Builder etherBuilder = new EthernetPacket.Builder();
+ etherBuilder.dstAddr(remoteMacAddress)
+ .srcAddr(localMacAddress)
+ .type(EtherType.IPV4)
+ .paddingAtBuild(true);
+ etherBuilder.payloadBuilder(
+ new AbstractPacket.AbstractBuilder() {
+ @Override
+ public Packet build() {
+ return ipV4Builder.build();
+ }
+ }
+ );
+
+ Packet p = etherBuilder.build();
+ System.out.println(p.toString());
+ sendHandle.sendPacket(p);
+ } catch (PcapNativeException | NotOpenException e) {
+ throw new RawSocketException("Error sending packet.", e);
+ } finally {
+ if((sendHandle != null) && sendHandle.isOpen()) {
+ sendHandle.close();
+ }
+ }
+ }
+
+ public void addListener(RawSocketListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeListener(RawSocketListener listener) {
+ listeners.remove(listener);
+ }
+
+ protected MacAddress getMacAddress(InetAddress address) throws RawSocketException {
+ if(!arpCache.containsKey(address)) {
+ MacAddress macAddress = lookupMacAddress(address);
+ arpCache.put(address, macAddress);
+ return macAddress;
+ }
+ return arpCache.get(address);
+ }
+
+ protected MacAddress lookupMacAddress(InetAddress remoteIpAddress) throws RawSocketException {
+ try {
+ PcapHandle receiveHandle
+ = nif.openLive(SNAPLEN, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
+ PcapHandle sendHandle
+ = nif.openLive(SNAPLEN, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
+
+ // Setup the filter to accept only the arp packets sent back to the current
+ // host from the address of the remote host we wanted to get the mac address
+ // from.
+ receiveHandle.setFilter(
+ "arp and src host " + remoteIpAddress.getHostAddress()
+ + " and dst host " + localIpAddress.getHostAddress()
+ + " and ether dst " + localMacAddress.toString(),
+ BpfProgram.BpfCompileMode.OPTIMIZE
+ );
+
+ CompletableFuture<MacAddress> resolutionFuture = new CompletableFuture<>();
+ PacketListener listener
+ = packet -> {
+ if (packet.contains(ArpPacket.class)) {
+ ArpPacket arp = packet.get(ArpPacket.class);
+ if (arp.getHeader().getOperation().equals(ArpOperation.REPLY)) {
+ resolutionFuture.complete(arp.getHeader().getSrcHardwareAddr());
+ }
+ }
+ };
+
+ pool.execute(() -> {
+ try {
+ receiveHandle.loop(-1, listener);
+ } catch (PcapNativeException | InterruptedException | NotOpenException e) {
+ // TODO: This is not nice ... fix this ...
+ throw new RuntimeException("Error receiving ARP lookup", e);
+ }
+ });
+
+ ArpPacket.Builder arpBuilder = new ArpPacket.Builder();
+ arpBuilder
+ .hardwareType(ArpHardwareType.ETHERNET)
+ .protocolType(EtherType.IPV4)
+ .hardwareAddrLength((byte) MacAddress.SIZE_IN_BYTES)
+ .protocolAddrLength((byte) ByteArrays.INET4_ADDRESS_SIZE_IN_BYTES)
+ .operation(ArpOperation.REQUEST)
+ .srcHardwareAddr(localMacAddress)
+ .srcProtocolAddr(localIpAddress)
+ .dstHardwareAddr(MacAddress.ETHER_BROADCAST_ADDRESS)
+ .dstProtocolAddr(remoteIpAddress);
+
+ EthernetPacket.Builder etherBuilder = new EthernetPacket.Builder();
+ etherBuilder.dstAddr(MacAddress.ETHER_BROADCAST_ADDRESS)
+ .srcAddr(localMacAddress)
+ .type(EtherType.ARP)
+ .payloadBuilder(arpBuilder)
+ .paddingAtBuild(true);
+
+ Packet p = etherBuilder.build();
+ sendHandle.sendPacket(p);
+ try {
+ return resolutionFuture.get(10000, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ return null;
+ }
+ } catch (PcapNativeException | InterruptedException | ExecutionException | NotOpenException e) {
+ throw new RawSocketException("Error looking up mac address.", e);
+ } finally {
+ /*if (receiveHandle.isOpen()) {
+ receiveHandle.close();
+ }
+ if (sendHandle.isOpen()) {
+ sendHandle.close();
+ }
+ if (!pool.isShutdown()) {
+ pool.shutdownNow();
+ }*/
+ }
+ }
+
+ /**
+ * Iterate through all devices and find the first that would be able to connect to the given address.
+ *
+ * @param remoteAddress address we want to connect to.
+ * @return PcapNetworkInterface interface that should be able to connect to the given address.
+ * @throws Exception something went wrong.
+ */
+ protected InetAddress getLocalNetworkAddress(InetAddress remoteAddress) throws RawSocketException {
+ byte[] remoteIp = remoteAddress.getAddress();
+ // Iterate over all network interfaces.
+ try {
+ for (PcapNetworkInterface dev : Pcaps.findAllDevs()) {
+ System.out.println(dev.getName());
+ // Iterate over all addresses configured for this interface.
+ for (PcapAddress addr : dev.getAddresses()) {
+ // Only check addresses matching the IP-version of the remote address.
+ if (remoteAddress.getClass().equals(addr.getAddress().getClass())) {
+ byte[] localIp = addr.getAddress().getAddress();
+ byte[] netMask = addr.getNetmask().getAddress();
+ boolean matches = true;
+ // Iterate over all bytes of the address and see if they match
+ // after applying the net mask filter.
+ for (int i = 0; i < localIp.length; i++) {
+ if ((localIp[i] & netMask[i]) != (remoteIp[i] & netMask[i])) {
+ matches = false;
+ break;
+ }
+ }
+ // If the current address would be able to connect to the remote
+ // address, return this device.
+ if (matches) {
+ return addr.getAddress();
+ }
+ }
+ }
+ }
+ } catch (PcapNativeException e) {
+ throw new RawSocketException("Error finding a device to communicate with remote address.", e);
+ }
+ return null;
+ }
+
+}
diff --git a/plc4j/utils/raw-sockets/src/main/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocketException.java b/plc4j/utils/raw-sockets/src/main/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocketException.java
new file mode 100644
index 0000000..f0828c4
--- /dev/null
+++ b/plc4j/utils/raw-sockets/src/main/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocketException.java
@@ -0,0 +1,16 @@
+package org.apache.plc4x.java.utils.rawsockets.netty;
+
+public class RawSocketException extends Exception {
+
+ public RawSocketException() {
+ }
+
+ public RawSocketException(String message) {
+ super(message);
+ }
+
+ public RawSocketException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/plc4j/utils/raw-sockets/src/main/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocketListener.java b/plc4j/utils/raw-sockets/src/main/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocketListener.java
new file mode 100644
index 0000000..52901a6
--- /dev/null
+++ b/plc4j/utils/raw-sockets/src/main/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocketListener.java
@@ -0,0 +1,7 @@
+package org.apache.plc4x.java.utils.rawsockets.netty;
+
+public interface RawSocketListener {
+
+ void packetReceived(byte[] rawData);
+
+}
diff --git a/plc4j/utils/raw-sockets/src/test/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocketTest.java b/plc4j/utils/raw-sockets/src/test/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocketTest.java
new file mode 100644
index 0000000..42178a2
--- /dev/null
+++ b/plc4j/utils/raw-sockets/src/test/java/org/apache/plc4x/java/utils/rawsockets/netty/RawSocketTest.java
@@ -0,0 +1,44 @@
+package org.apache.plc4x.java.utils.rawsockets.netty;
+
+import java.util.Arrays;
+
+public class RawSocketTest {
+
+ public static void main(String[] args) throws Exception {
+ // Protocol number 1 = ICMP (Ping)
+ RawSocket rawSocket = new RawSocket(1);
+
+ // Simply print the result to the console
+ rawSocket.addListener(new RawSocketListener() {
+ @Override
+ public void packetReceived(byte[] rawData) {
+ System.out.println("Got response:");
+ System.out.println(Arrays.toString(rawData));
+ }
+ });
+
+ // Connect to the remote address
+ // This doesn't really connect in the usual sense, it
+ // does the ARP MAC address lookup and sets up the listener
+ // to accept packets sent from that mac address to the
+ // current machines with the given IP protocol id.
+ rawSocket.connect("10.10.56.1");
+
+ // Simple ICMP (Ping packet)
+ byte[] rawData = new byte[] {
+ // Type (ICMP Ping Request) & Code (just 0)
+ (byte) 0x08, (byte) 0x00,
+ // Checksum
+ (byte) 0xe3, (byte) 0xe5,
+ // Identifier
+ (byte) 0x00, (byte) 0x01,
+ // Sequence Number
+ (byte) 0x00, (byte) 0x00,
+ // Payload (Just random data that was used to fit to the checksum)
+ (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, (byte) 0x09};
+
+ // Write the raw packet to the remote host.
+ rawSocket.write(rawData);
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
cdutz@apache.org.