/*
* Copyright 2010 NCHOVY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.krakenapps.pcap.util;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.krakenapps.pcap.decoder.arp.ArpDecoder;
import org.krakenapps.pcap.decoder.arp.ArpPacket;
import org.krakenapps.pcap.decoder.arp.ArpProcessor;
import org.krakenapps.pcap.decoder.ethernet.EthernetDecoder;
import org.krakenapps.pcap.decoder.ethernet.EthernetFrame;
import org.krakenapps.pcap.decoder.ethernet.EthernetHeader;
import org.krakenapps.pcap.decoder.ethernet.EthernetType;
import org.krakenapps.pcap.decoder.ethernet.MacAddress;
import org.krakenapps.pcap.live.AddressBinding;
import org.krakenapps.pcap.live.PcapDevice;
import org.krakenapps.pcap.live.PcapDeviceManager;
import org.krakenapps.pcap.live.PcapDeviceMetadata;
import org.krakenapps.pcap.packet.PcapPacket;
import org.krakenapps.pcap.routing.RoutingEntry;
import org.krakenapps.pcap.routing.RoutingTable;
/**
* @author xeraph
*/
public class Arping {
public static Map<InetAddress, MacAddress> scan(String deviceName, int timeout) throws InterruptedException,
IOException {
PcapDeviceMetadata metadata = PcapDeviceManager.getDeviceMetadata(deviceName);
return scan(metadata.getName(), metadata.getSubnet(), metadata.getNetmask(), timeout);
}
public static Map<InetAddress, MacAddress> scan(String deviceName, InetAddress network, InetAddress mask,
int timeout) throws InterruptedException, IOException {
return scan(deviceName, network, mask, timeout, 0);
}
public static Map<InetAddress, MacAddress> scan(String deviceName, InetAddress network, InetAddress mask,
int timeout, int ipg) throws InterruptedException, IOException {
if (!(network instanceof Inet4Address) || !(mask instanceof Inet4Address))
throw new IllegalArgumentException("network address should be IPv4 address");
PcapDevice device = null;
try {
device = PcapDeviceManager.open(deviceName, 1000);
device.setFilter("arp", false);
ArpListener listener = new ArpListener(device);
Thread t = new Thread(listener);
t.start();
// send request
int net = IpConverter.toInt((Inet4Address) network);
int m = IpConverter.toInt((Inet4Address) mask);
int from = (net & m);
int to = from | ~m;
for (int ip = from; ip <= to; ip++) {
InetAddress targetIp = IpConverter.toInetAddress(ip);
Buffer b = preparePacket(device.getMetadata(), targetIp);
device.write(b);
if (ipg > 0)
Thread.sleep(ipg);
}
Thread.sleep(timeout);
listener.stop = true;
t.interrupt();
return listener.table;
} finally {
// double check (listener should already closed device)
if (device != null && device.isOpen())
device.close();
}
}
private static class ArpListener implements Runnable {
private volatile boolean stop = false;
private PcapDevice device;
private EthernetDecoder eth;
private Map<InetAddress, MacAddress> table;
public ArpListener(PcapDevice device) {
this.device = device;
eth = new EthernetDecoder();
table = new HashMap<InetAddress, MacAddress>();
}
@Override
public void run() {
ArpCallback callback = buildArpStack(eth);
try {
while (!stop) {
PcapPacket p = device.getPacket();
eth.decode(p);
addToCache(table, callback);
}
} catch (IOException e) {
} finally {
try {
if (device != null && device.isOpen())
device.close();
} catch (IOException e) {
}
}
}
}
public static Map<InetAddress, MacAddress> scan(String deviceName, Collection<InetAddress> targets, int timeout)
throws IOException {
Map<InetAddress, MacAddress> table = new HashMap<InetAddress, MacAddress>();
EthernetDecoder eth = new EthernetDecoder();
ArpCallback callback = buildArpStack(eth);
PcapDevice device = PcapDeviceManager.open(deviceName, timeout);
device.setFilter("arp", false);
device.getPacket();
PcapDeviceMetadata metadata = PcapDeviceManager.getDeviceMetadata(deviceName);
for (InetAddress target : targets) {
Buffer b = preparePacket(metadata, target);
if (b != null)
device.write(b);
}
long begin = new Date().getTime();
while (true) {
try {
PcapPacket packet = device.getPacket();
eth.decode(packet);
addToCache(table, callback);
} catch (IOException e) {
if (!e.getMessage().equals("Timeout"))
throw e;
}
long end = new Date().getTime();
if (end - begin > timeout)
break;
}
device.close();
return table;
}
private static void addToCache(Map<InetAddress, MacAddress> table, ArpCallback callback) {
ArpPacket reply = callback.getLast();
if (reply != null && reply.getOpcode() == 0x2) {
table.put(reply.getSenderIp(), reply.getSenderMac());
}
}
public static MacAddress query(InetAddress targetIp, int timeout) throws IOException {
RoutingEntry entry = RoutingTable.findRoute(targetIp);
if (entry == null)
throw new IllegalStateException("route not found for " + targetIp.getHostAddress());
PcapDeviceMetadata metadata = PcapDeviceManager.getDeviceMetadata(entry.getInterfaceName());
if (metadata == null)
throw new IllegalStateException("interface not found for " + targetIp.getHostAddress());
if (metadata.isIntranet(targetIp))
return Arping.query(metadata.getName(), targetIp, timeout);
return Arping.query(metadata.getName(), entry.getGateway(), timeout);
}
public static MacAddress query(String deviceName, InetAddress targetIp, int timeout) throws IOException {
EthernetDecoder eth = new EthernetDecoder();
ArpCallback callback = buildArpStack(eth);
PcapDeviceMetadata metadata = PcapDeviceManager.getDeviceMetadata(deviceName);
if (metadata == null)
throw new IllegalArgumentException("pcap device not found: " + deviceName);
Buffer b = preparePacket(metadata, targetIp);
PcapDevice device = PcapDeviceManager.open(metadata.getName(), timeout);
device.setFilter("arp", false);
if (b != null) {
device.write(b);
}
ArpPacket last = null;
long begin = new Date().getTime();
while (true) {
try {
PcapPacket packet = device.getPacket();
eth.decode(packet);
ArpPacket reply = callback.getLast();
if (reply != null && reply.getOpcode() == 0x2 && reply.getSenderIp().equals(targetIp)) {
last = reply;
break;
}
} catch (IOException e) {
if (!e.getMessage().equals("Timeout"))
throw e;
}
long end = new Date().getTime();
if (end - begin > timeout)
break;
}
device.close();
return last == null ? null : last.getSenderMac();
}
private static ArpCallback buildArpStack(EthernetDecoder eth) {
ArpDecoder arp = new ArpDecoder();
ArpCallback callback = new ArpCallback();
eth.register(EthernetType.ARP, arp);
arp.register(callback);
return callback;
}
private static Buffer preparePacket(PcapDeviceMetadata metadata, InetAddress targetIp) {
MacAddress senderMac = metadata.getMacAddress();
InetAddress senderIp = null;
for (AddressBinding binding : metadata.getBindings()) {
if (binding.getAddress() instanceof Inet4Address) {
senderIp = binding.getAddress();
break;
}
}
if (senderIp == null)
return null;
ArpPacket p = ArpPacket.createRequest(senderMac, senderIp, targetIp);
MacAddress broadcastMac = new MacAddress("ff:ff:ff:ff:ff:ff");
EthernetHeader ethernetHeader = new EthernetHeader(senderMac, broadcastMac, EthernetType.ARP);
EthernetFrame frame = new EthernetFrame(ethernetHeader, p.getBuffer());
return frame.getBuffer();
}
private static class ArpCallback implements ArpProcessor {
private ArpPacket last;
@Override
public void process(ArpPacket p) {
last = p;
}
public ArpPacket getLast() {
return last;
}
}
}