package com.netifera.platform.net.internal.pcap.linux; import java.nio.ByteBuffer; import java.nio.ByteOrder; import com.netifera.platform.api.log.ILogger; import com.netifera.platform.api.system.ISystemService; import com.netifera.platform.net.internal.pcap.INativePacketCapture; import com.netifera.platform.net.internal.pcap.IPacketCaptureInternal; import com.netifera.platform.net.internal.pcap.PacketHeader; import com.netifera.platform.net.pcap.Datalink; import com.netifera.platform.net.pcap.IPacketHandler; import com.netifera.platform.net.pcap.IPacketCapture.PcapDirection; public class LinuxPacketCapture implements INativePacketCapture { private final LinuxPcapOpen openHelper; private final ISystemService system; private final IPacketCaptureInternal pcap; //private final ILogger logger; private int socket = -1; private int loopbackIndex; private int interfaceIndex; private boolean cooked; private Datalink linktype; private int offset; private int snaplen; private int timeout; private ByteBuffer packetBuffer; private byte[] packetBufferArray; //private byte[] buffer; private PcapDirection direction; public LinuxPacketCapture(ISystemService system, IPacketCaptureInternal pcap, ILogger logger) { this.system = system; this.pcap = pcap; //this.logger = logger; openHelper = new LinuxPcapOpen(this, system, logger); } public synchronized void close() { if(socket == -1) { return; } system.syscall_close(socket); socket = -1; } private synchronized boolean isClosed() { return socket == -1; } public boolean openLive(String device, int snaplen, int timeout, boolean promiscuous) { if(!openHelper.socketOpenLive(device, promiscuous)) { return false; } direction = PcapDirection.PCAP_D_INOUT; /* We can safely pass "recvfrom()" a byte count * based on the snapshot length. * * If we're in cooked mode, make the snapshot length * large enough to hold a "cooked mode" header plus * 1 byte of packet data (so we don't pass a byte * count of 0 to "recvfrom()"). */ if(cooked && (snaplen < Constants.SLL_HDR_LEN)) { snaplen = Constants.SLL_HDR_LEN + 1; } packetBuffer = ByteBuffer.allocateDirect(snaplen + offset); if(!packetBuffer.hasArray()) { packetBuffer = ByteBuffer.allocate(snaplen + offset); if(!packetBuffer.hasArray()) { fail("Cannot allocate an array backed ByteBuffer"); return false; } } packetBuffer.order(ByteOrder.nativeOrder()); packetBufferArray = packetBuffer.array(); this.snaplen = snaplen; this.timeout = timeout; return true; } public Datalink getLinkType() { return linktype; } void setLinkType(Datalink dlt) { this.linktype = dlt; } int getInterfaceIndex() { return interfaceIndex; } void setInterfaceIndex(int index) { interfaceIndex = index; } void setLoopbackIndex(int index) { loopbackIndex = index; } void setCooked(boolean cooked) { this.cooked = cooked; } int getSocket() { return socket; } void setSocket(int s) { socket = s; } void dltListClear() { pcap.dltListClear(); } void dltListAdd(Datalink dlt) { pcap.dltListAdd(dlt); } void setOffset(int offset) { this.offset = offset; } public boolean packetRead(IPacketHandler handler) { int readOffset = cooked ? Constants.SLL_HDR_LEN : 0; byte[] fds = new byte[8]; system.pack32(fds, 0, socket); system.pack16(fds, 4, Constants.POLLIN); system.pack16(fds, 6, 0); final int ret = system.syscall_poll(fds, timeout); if(ret < 0) { if(isClosed()) return true; fail("poll() failed while reading a packet", system.getErrno()); return false; } final int revents = system.unpack16(fds, 6); if(ret == 0 ||(revents & Constants.POLLIN) == 0) { return true; } byte[] sockaddr_ll = new byte[20]; int len = system.syscall_recvfrom(socket, packetBufferArray, offset + readOffset, snaplen - readOffset, Constants.MSG_TRUNC, sockaddr_ll, 20); if(len < 0) { if(isClosed()) return true; fail("Error reading from packet socket", system.getErrno()); return false; } packetBuffer.position(offset); int sll_ifindex = system.unpack32(sockaddr_ll, 4); int sll_pkttype = (sockaddr_ll[10] & 0xFF); /* * Unfortunately, there is a window between socket() and * bind() where the kernel may queue packets from any * interface. If we're bound to a particular interface, * discard packets not from that interface. * * (If socket filters are supported, we could do the * same thing we do when changing the filter; however, * that won't handle packet sockets without socket * filter support, and it's a bit more complicated. * It would save some instructions per packet, however.) */ if(sll_ifindex != interfaceIndex) { return true; } if(sll_pkttype == Constants.PACKET_OUTGOING) { /* * Outgoing packet. * If this is from the loopback device, reject it; * we'll see the packet as an incoming packet as well, * and we don't want to see it twice. */ if(sll_ifindex == loopbackIndex) { return true; } /* * If the user only wants incoming packets, reject it. */ if(direction == PcapDirection.PCAP_D_IN) { return true; } } else { /* * Incoming packet. * If the user only wants outgoing packets, reject it. */ if(direction == PcapDirection.PCAP_D_OUT) { return true; } } if(cooked) { len += Constants.SLL_HDR_LEN; /* * Map the PACKET_ value to a LINUX_SLL_ value; we * want the same numerical value to be used in * the link-layer header even if the numerical values * for the PACKET_ #defines change, so that programs * that look at the packet type field will always be * able to handle DLT_LINUX_SLL captures. */ final int packetType; switch(sll_pkttype) { case Constants.PACKET_HOST: packetType = Constants.LINUX_SLL_HOST; break; case Constants.PACKET_BROADCAST: packetType = Constants.LINUX_SLL_BROADCAST; break; case Constants.PACKET_MULTICAST: packetType = Constants.LINUX_SLL_MULTICAST; break; case Constants.PACKET_OTHERHOST: packetType = Constants.LINUX_SLL_OTHERHOST; break; case Constants.PACKET_OUTGOING: packetType = Constants.LINUX_SLL_OUTGOING; break; default: packetType = -1; } final int sll_hatype = system.unpack16(sockaddr_ll, 8); final int sll_halen = (sockaddr_ll[11] & 0xFF); final int sll_protocol = system.unpack16(sockaddr_ll, 2); packetBuffer.mark(); packetBuffer.putShort((short) system.htons(packetType)); packetBuffer.putShort((short) system.htons(sll_hatype)); packetBuffer.putShort((short) system.htons(sll_halen)); packetBuffer.put(sockaddr_ll, 12, 8); packetBuffer.putShort((short) sll_protocol); packetBuffer.reset(); } int caplen = len; if(caplen > snaplen) { caplen = snaplen; } /* XXX run userland filter here if needed */ byte[] timeval = new byte[8]; if(system.syscall_ioctl(socket, Constants.SIOCGSTAMP, timeval, 0, 8) == -1) { fail("SIOCGSTAMP failed on packet socket", system.getErrno()); return false; } int seconds = system.unpack32(timeval, 0); int useconds = system.unpack32(timeval, 4); handler.handlePacket(packetBuffer.slice(), new PacketHeader(seconds, useconds, caplen, len)); return true; } void fail(String message) { fail(message, 0); } void fail(String message, int errno) { if(errno != 0) { pcap.setError(message + " : " + system.getErrorMessage(errno)); } else { pcap.setError(message); } if(!isClosed()) close(); dltListClear(); } public int getFileDescriptor() { return socket; } public boolean setDataLink(Datalink dlt) { pcap.setError("Setting datalink not supported on linux"); return false; } }