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.