/*_########################################################################## _## _## Copyright (C) 2011-2013 Kaito Yamada _## _########################################################################## */ package com.github.kaitoy.sneo.network; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.pcap4j.core.BpfProgram.BpfCompileMode; import org.pcap4j.core.NotOpenException; import org.pcap4j.core.PacketListener; import org.pcap4j.core.PcapHandle; import org.pcap4j.core.PcapNativeException; import org.pcap4j.core.PcapNetworkInterface; import org.pcap4j.core.PcapNetworkInterface.PromiscuousMode; import org.pcap4j.core.Pcaps; import org.pcap4j.packet.EthernetPacket; import org.pcap4j.packet.IcmpV4CommonPacket; import org.pcap4j.packet.IllegalPacket; import org.pcap4j.packet.IpV4Packet; import org.pcap4j.packet.Packet; import org.pcap4j.packet.UnknownPacket; import org.pcap4j.packet.namednumber.EtherType; import org.pcap4j.util.MacAddress; import org.pcap4j.util.NifSelector; import org.snmp4j.log.LogAdapter; import org.snmp4j.log.LogFactory; import com.github.kaitoy.sneo.network.protocol.EthernetHelper; import com.github.kaitoy.sneo.network.protocol.IpV6Helper; import com.github.kaitoy.sneo.util.NamedThreadFactory; public final class RealNetworkInterface implements NetworkInterface { private static final LogAdapter logger = LogFactory.getLogger(RealNetworkInterface.class); private static final int MAX_INBOUND_MESSAGE_SIZE = 65536; private static final int PCAP_READ_TIMEOUT = 10; private static final int SEND_PACKET_RETRY_COUNT = 5; private static final long SHUTDOWN_TIMEOUT = 2000; private static final TimeUnit SHUTDOWN_TIMEOUT_UNIT = TimeUnit.MILLISECONDS; private final String name; private final MacAddress macAddress; private final List<MacAddress> multicastMacAddresses = Collections.synchronizedList(new ArrayList<MacAddress>()); private final List<NifIpAddress> ipAddresses = Collections.synchronizedList(new ArrayList<NifIpAddress>()); private final PacketListener host; private final PcapNetworkInterface pcapNif; private final PcapHandle handle2capture; private final PcapHandle handle2send; private final ExecutorService packetCaptorExecutor; private final Random random = new Random(System.currentTimeMillis()); private final Object thisLock = new Object(); private volatile boolean running = false; public RealNetworkInterface( String name, MacAddress macAddress, String deviceName, PacketListener host ) { if ( name == null || macAddress == null || host == null ) { StringBuilder sb = new StringBuilder(80); sb.append("name: ").append(name) .append(" macAddress: ").append(macAddress) .append(" host: ").append(host); throw new NullPointerException(sb.toString()); } this.name = name; this.macAddress = macAddress; this.host = host; this.packetCaptorExecutor = Executors.newSingleThreadExecutor( new NamedThreadFactory( name + "_" + PacketCaptor.class.getSimpleName(), true ) ); PcapNetworkInterface pnif = null; InetAddress rnifAddr = NetworkPropertiesLoader.getRealNetworkInterfaceIpAddress(); if (rnifAddr != null) { try { pnif = Pcaps.getDevByAddress(rnifAddr); if (pnif == null) { throw new IllegalStateException("No NIF has " + rnifAddr); } } catch (PcapNativeException e) { throw new IllegalStateException(e); } } else if (deviceName != null){ try { pnif = Pcaps.getDevByName(deviceName); } catch (PcapNativeException e) { throw new IllegalStateException(e); } } else { try { pnif = new NifSelector().selectNetworkInterface(); if (pnif == null) { throw new IllegalStateException("No NIF was selected."); } } catch (IOException e) { throw new IllegalStateException(e); } } this.pcapNif = pnif; try { this.handle2capture = this.pcapNif.openLive( MAX_INBOUND_MESSAGE_SIZE, PromiscuousMode.PROMISCUOUS, PCAP_READ_TIMEOUT ); this.handle2send = this.pcapNif.openLive( MAX_INBOUND_MESSAGE_SIZE, PromiscuousMode.PROMISCUOUS, PCAP_READ_TIMEOUT ); } catch (PcapNativeException e) { throw new IllegalStateException(e); } StringBuilder sb = new StringBuilder(); sb.append("(") .append("ether dst ") .append(Pcaps.toBpfString(macAddress)) .append(")") .append(" or ") .append("(") .append("arp and ether dst ") // ARP request .append(Pcaps.toBpfString(MacAddress.ETHER_BROADCAST_ADDRESS)) .append(")") .append(" or ") .append("(") // Destination Mac address starts with 0x3333FF, // which indicates it is Neighbor Solicitation .append("icmp6 and ether[0:4] & 0xFFFFFF00 = 0x3333FF00") .append(")"); try { logger.info("Filtering expression: " + sb); handle2capture.setFilter( sb.toString(), BpfCompileMode.OPTIMIZE ); } catch (PcapNativeException e) { throw new AssertionError(e); } catch (NotOpenException e) { throw new AssertionError("Never get here."); } } public String getName() { return name; } public MacAddress getMacAddress() { return macAddress; } public boolean isTrunk() { return false; } public List<NifIpAddress> getIpAddresses() { return new ArrayList<NifIpAddress>(ipAddresses); } public void addIpAddress(NifIpAddress addr) { if (addr instanceof NifIpV6Address) { multicastMacAddresses.add( IpV6Helper.generateNeighborSolicitationMacAddress( IpV6Helper.generateSolicitedNodeMulticastAddress( ((NifIpV6Address)addr).getIpAddr()) ) ); } ipAddresses.add(addr); } @Deprecated public void addUser(PacketListener user) { throw new UnsupportedOperationException(); } public void start() { synchronized (thisLock) { if (running) { logger.warn("Already started."); return; } packetCaptorExecutor.execute(new PacketCaptor()); running = true; } logger.info("started"); } public void stop() { synchronized (thisLock) { if (!running) { logger.warn("Already stopped."); return; } handle2capture.breakLoop(); running = false; } logger.info("stopped"); } public boolean isRunning() { return running; } public void sendPacket(Packet packet) { if (!running) { logger.warn("Not running. Can't send packet: " + packet); return; } for (int i = 1; i <= SEND_PACKET_RETRY_COUNT; i ++) { try { handle2send.sendPacket(packet); if (logger.isDebugEnabled()) { logger.debug("Sent a packet: " + packet); } return; } catch (PcapNativeException e) { logger.warn("Failed to send a packet. Retry: " + i); try { Thread.sleep(0, random.nextInt(1000000)); } catch (InterruptedException ie) { logger.warn(ie); break; } } catch (NotOpenException e) { throw new AssertionError("Never get here."); } } logger.error("Couldn't send packet: " + packet); } public void shutdown() { synchronized (thisLock) { if (running) { stop(); } packetCaptorExecutor.shutdown(); try { boolean terminated = packetCaptorExecutor.awaitTermination( SHUTDOWN_TIMEOUT, SHUTDOWN_TIMEOUT_UNIT ); if (!terminated) { logger.warn("Termination timeout occured."); EthernetPacket.Builder eb = new EthernetPacket.Builder(); eb.type(EtherType.ARP) .srcAddr(macAddress) .dstAddr(MacAddress.ETHER_BROADCAST_ADDRESS) .paddingAtBuild(true) .payloadBuilder(new UnknownPacket.Builder().rawData(new byte[5])); try { logger.info("Try to send a bogus packet to let the captor break."); handle2send.sendPacket(eb.build()); } catch (PcapNativeException e) { logger.error(e); } catch (NotOpenException e) { throw new AssertionError("Never get here."); } } } catch (InterruptedException e) { logger.warn(e); } handle2capture.close(); handle2send.close(); } logger.info("A real network interface has been shutdown."); } private class PacketCaptor implements Runnable { public void run() { if (handle2capture == null || !handle2capture.isOpen()) { throw new IllegalStateException("handle: " + handle2capture); } logger.info("start."); try { handle2capture.loop(-1, new PacketListenerImpl()); } catch (PcapNativeException e) { logger.error(e); } catch (InterruptedException e) { logger.info("broken."); } catch (NotOpenException e) { throw new AssertionError("Never get here."); } logger.info("stopped."); } } private class PacketListenerImpl implements PacketListener { public void gotPacket(Packet packet) { if (logger.isDebugEnabled()) { logger.debug("Received a packet: " + packet); } if (!(packet instanceof EthernetPacket)) { if (logger.isDebugEnabled()) { logger.debug("Dropped an unknown packet: " + packet); } return; } if (!EthernetHelper.matchesDestination(packet, macAddress)) { MacAddress dstAddr = packet.get(EthernetPacket.class).getHeader().getDstAddr(); if (!multicastMacAddresses.contains(dstAddr)) { if (logger.isDebugEnabled()) { StringBuilder sb = new StringBuilder(); logger.debug( sb.append("Dropped a packet not to me(") .append(macAddress) .append("): ") .append(packet) .toString() ); } return; } } IpV4Packet ipv4 = packet.get(IpV4Packet.class); if (ipv4 != null && !ipv4.getHeader().hasValidChecksum(true)) { logger.warn("Dropped an invalid IPv4 packet: " + packet); return; } IcmpV4CommonPacket icmpv4 = packet.get(IcmpV4CommonPacket.class); if (icmpv4 != null && !icmpv4.hasValidChecksum(true)) { logger.warn("Dropped an invalid ICMPv4 packet: " + packet); return; } // UdpPacket udp = packet.get(UdpPacket.class); // if (udp != null && !udp.hasValidChecksum(srcAddr, dstAddr, true)) { // logger.warn("Dropped an invalid ICMPv4 packet: " + packet); // return; // } if (packet.contains(IllegalPacket.class)) { logger.warn("Dropped a broken packet: " + packet); return; } host.gotPacket(packet); } } }