package com.netifera.platform.net.internal.pcap.linux; import com.netifera.platform.api.log.ILogger; import com.netifera.platform.api.system.ISystemService; import com.netifera.platform.net.pcap.Datalink; public class LinuxPcapOpen { private final static int BACKDOOR_COOKED_SOCKET = 0; private final static int BACKDOOR_RAW_SOCKET = 1; private final LinuxPacketCapture pcap; private final ISystemService system; private final ILogger logger; private final LinuxArpMap arpMap; LinuxPcapOpen(LinuxPacketCapture pcap, ISystemService system, ILogger logger) { this.pcap = pcap; this.system = system; this.logger = logger; arpMap = new LinuxArpMap(pcap); } boolean socketOpenLive(String device, boolean promiscuous) { if(device == null || device.length() == 0) { pcap.fail("No device specified"); return false; } if(!openRaw(device)) { return false; } int idx = getInterfaceIndex("lo"); pcap.setLoopbackIndex(idx); idx = getInterfaceIndex(device); if(idx == -1) { pcap.fail("Interface index lookup failed"); return false; } pcap.setInterfaceIndex(idx); if(!bindInterface()) { return false; } if(promiscuous) { enablePromiscuous(device); } return true; } /* * Open a PF_PACKET socket, store the file descriptor in the * 'fd' field and set the 'linktype' field to the appropriate * DLT_XXX constant. If the linktype cannot be determined, or * if it is a device type that runs only in cooked mode, reopen * a cooked socket and set the 'linktype' to DLT_LINUX_SLL if * necessary. * * The Linux 'any' device is not supported. */ @SuppressWarnings("incomplete-switch") private boolean openRaw(String device) { if(!openSocket(false)) { return false; } pcap.setCooked(false); pcap.setOffset(0); /* * What kind of frames do we have to deal with? Fall back * to cooked mode if we have an unknown interface type. */ int arptype = getInterfaceArpType(device); if(arptype == -1) { return false; } if(!arpMap.mapToDlt(arptype, true)) { pcap.setLinkType(Datalink.DLT_NULL); } /* * Unknown interface type (-1), or a * device we explicitly chose to run * in cooked mode (e.g., PPP devices), * or an ISDN device (whose link-layer * type we can only determine by using * APIs that may be different on different * kernels) - reopen in cooked mode. */ switch(pcap.getLinkType()) { case DLT_NULL: case DLT_LINUX_SLL: case DLT_LINUX_IRDA: case DLT_LINUX_LAPD: // TODO DLT_EN10MB and isdn || isdY return openCooked(device); } return true; } /* Called from openRaw() to fallback to a cooked socket */ private boolean openCooked(String device) { if(!openSocket(true)) { return false; } pcap.setCooked(true); pcap.dltListClear(); if(pcap.getLinkType() != Datalink.DLT_LINUX_IRDA && pcap.getLinkType() != Datalink.DLT_LINUX_LAPD) { pcap.setLinkType(Datalink.DLT_LINUX_SLL); } return true; } /* * Open either a cooked or raw socket depending on the * value of the 'cooked' argument. */ private boolean openSocket(boolean cooked) { if(pcap.getSocket() != -1) { pcap.close(); } final int type = cooked ? (Constants.SOCK_DGRAM) : (Constants.SOCK_RAW); final int s = system.syscall_socket(Constants.PF_PACKET, type, system.htons(Constants.ETH_P_ALL)); if(s < 0) { final int errno = system.getErrno(); if(errno == Constants.EPERM) { return backdoorOpenSocket(cooked); } pcap.fail("Error opening capture socket", errno); return false; } pcap.setSocket(s); return true; } private boolean backdoorOpenSocket(boolean cooked) { final int request = cooked ? (BACKDOOR_COOKED_SOCKET) : (BACKDOOR_RAW_SOCKET); final int s = system.backdoor_request(request); if(s < 0) { final int errno = system.getErrno(); if(errno == Constants.EPERM) { pcap.fail("Permission error opening socket with backdoor. (Is backdoor setuid?)"); return false; } pcap.fail("Error attempting to open capture socket with backdoor", errno); return false; } pcap.setSocket(s); return true; } private byte[] deviceNameToStruct(String device) { if(device.length() >= Constants.IFNAMSIZ) { pcap.fail("Device name too long " + device); return null; } // struct ifreq final byte[] ifreq = new byte[40]; for(int i = 0; i < device.length(); i++) { ifreq[i] = (byte) device.charAt(i); } return ifreq; } /* * Get the hardware type of the given interface as ARPHRD_xxx constant. */ private int getInterfaceArpType(String device) { final byte[] data = deviceNameToStruct(device); if(data == null) { return -1; } if(system.syscall_ioctl(pcap.getSocket(), Constants.SIOCGIFHWADDR, data, 40, 40) < 0) { if(system.getErrno() == Constants.ENODEV) { pcap.fail("Could not open device '" + device + "'"); return -1; } pcap.fail("ioctl SIOCGIFHWADDR failed", system.getErrno()); return -1; } return system.unpack16(data, Constants.IFNAMSIZ); } private int getInterfaceIndex(String device) { final byte[] data = deviceNameToStruct(device); if(system.syscall_ioctl(pcap.getSocket(), Constants.SIOCGIFINDEX, data, 40, 40) < 0) { pcap.fail("ioctl SIOCGIFINDEX failed", system.getErrno()); return -1; } return system.unpack32(data, Constants.IFNAMSIZ); } private boolean bindInterface() { final int s = pcap.getSocket(); final int idx = pcap.getInterfaceIndex(); if( (s == -1) || (idx == -1)) { pcap.fail("Socket or interface invalid"); return false; } final byte[] sockaddr_ll = new byte[20]; /* sll.sll_family = PF_PACKET */ system.pack16(sockaddr_ll, 0, Constants.PF_PACKET); /* sll.sll_ifindex = ifIndex */ system.pack32(sockaddr_ll, 4, idx); /* sll.sll_protocol = htons(ETH_P_ALL) */ system.pack16(sockaddr_ll, 2, system.htons(Constants.ETH_P_ALL)); if(system.syscall_bind(s, sockaddr_ll, 20) < 0) { pcap.fail("bind() interface failed", system.getErrno()); return false; } final byte[] errbuf = new byte[4]; if(system.syscall_getsockopt(s, Constants.SOL_SOCKET, Constants.SO_ERROR, errbuf, 4) < 0) { pcap.fail("getsockopt() failed", system.getErrno()); return false; } int errno = system.unpack32(errbuf, 0); if(errno != 0) { pcap.fail("SO_ERROR after bind()", errno); return false; } return true; } private void enablePromiscuous(String device) { /* sizeof(struct packet_mreq) = 16 */ byte[] data = new byte[16]; /* packet_mreq.mr_ifindex */ system.pack32(data, 0, pcap.getInterfaceIndex()); /* packet_mreq.mr_type */ system.pack16(data, 4, Constants.PACKET_MR_PROMISC); if(system.syscall_setsockopt(pcap.getSocket(), Constants.SOL_PACKET, Constants.PACKET_ADD_MEMBERSHIP, data, 16) < 0) { logger.warning("enablePromiscuous() failed for " + device + ": "+ system.getErrorMessage(system.getErrno())); // ignore failure } } }