我想实现基本的网络检查功能,以测试所提供的url是否响应(例如:ping www.google.com)。它必须提供操作信息,例如,所请求的服务不可用或无法到达主机。我能够使用icmp4j库实现它。但我想实现相同的使用pcap4j库。我想把url放在文本框中,然后点击连接按钮,它将调用pcap4j api来检查主机是否响应。
可以使用IcmpV4EchoPacket
、IcmpV4CommonPacket
、IpV4Packet
、EthernetPacket
的builder对以太网报文创建ICMPv4 Echo (ping),通过PcapHandle.sendPacket()
发送。请参考pcap4j-sample项目中的org.pcap4j.sample.SendFragmentedEcho
。
您将需要实现ARP来解析IP地址到MAC地址,如pcap4j-sample项目中的org.pcap4j.sample.SendArpRequest
。
你还需要实现一个功能,以某种方式从给定的IP地址找到下一跳(默认网关或左右)。Pcap4J没有提供支持此实现的API。(Java不提供获取路由表的API…)
你最好用java.net.InetAddress#isReachable()
代替。
我花了一年多的时间才弄清楚这一点,因为我想用pcap4j创建跟踪路由,所以我做了以下工作:
- 获取您的IPv4地址和Mac地址,这可以通过查询
PcapNetworkInterface
轻松实现 - 获取目标IP地址,如果您有DNS名称,您需要事先解析它。
- 获取目标Mac地址。
- 目标在同一子网:发送ARP请求来解析mac(或者,mac广播也可能工作良好)。
- 目标是在不同的子网:你需要得到你的网关服务器的mac,这不是那么容易。假设你有其他网络流量正在进行,你可以监听传入的数据包并获得源mac,其中源IP地址来自不同的子网,这可能是你的网关服务器的mac地址。
创建 - 监听传入的ICMP流量,您将得到以下三个中的一个:
-
IcmpV4EchoReplyPacket
,可能是对您请求的回答(检查标识符和序列号以确保) - 如果在指定的生存时间内无法达到目标,则
IcmpV4TimeExceededPacket
- 没有,路由器和ping目标可以自由忽略和不回答您的请求
-
IcmpV4EchoPacket
并发送需要填充的变量:
short IDENTIFIER; // identifer may be any 16 bit interger
short SEQUENCE; // sequence may be any 16 bit integer
byte TTL; // time to live (1-255)
Inet4Address IP_TARGET; // ip address of your ping target
Inet4Address IP_ORIGIN; // your own ip address
MacAddress MAC_TARGET; // target or gateway mac address
MacAddress MAC_SOURCE; // your own mac address
PcapNetworkInterface PCAP4J_NETWORK_INTERFACE; // network interface used to execute the ping
如何发送ICMP Echo Request报文(作为EthernetPacket
的IpV4Packet
的IcmpV4CommonPacket
的有效载荷):
public Packet buildPacket() {
IcmpV4EchoPacket.Builder icmpV4Echo = new IcmpV4EchoPacket.Builder()
.identifier(IDENTIFIER) // optional, default zero
.sequenceNumber(SEQUENCE); // optional, default zero
IcmpV4CommonPacket.Builder icmpV4Common = new IcmpV4CommonPacket.Builder()
.type(IcmpV4Type.ECHO) // type is echo
.code(IcmpV4Code.NO_CODE) // echo request doesn't need this
.payloadBuilder(icmpV4Echo)
.correctChecksumAtBuild(true);
IpV4Packet.Builder ipv4Builder = new IpV4Packet.Builder()
.correctChecksumAtBuild(true)
.correctLengthAtBuild(true)
.dstAddr(IP_TARGET) // IPv4 Address where tp send the request
.payloadBuilder(icmpV4Common)
.protocol(IpNumber.ICMPV4) // payload is ICMPV4
.srcAddr(IP_ORIGIN) // Your own IPv4 Address
.tos(IpV4Rfc1349Tos.newInstance((byte) 0))
.ttl(TTL) // time to live (1-255)
.version(IpVersion.IPV4); // IP Version is IPv4
EthernetPacket.Builder etherBuilder = new EthernetPacket.Builder()
.dstAddr(MAC_TARGET) // the targets mac address
.srcAddr(MAC_SOURCE) // your own mac address
.type(EtherType.IPV4) // payload protocl is IPv4
.payloadBuilder(ipv4Builder)
.paddingAtBuild(true);
return etherBuilder.build(); // build your packet
}
ICMP应答或超时的监听器:
public PacketListener buildListener() {
return new PacketListener() {
@Override
public void gotPacket(Packet packet) {
if (!(packet instanceof EthernetPacket))
return;
EthernetPacket ethernetPacket = (EthernetPacket) packet;
packet = ethernetPacket.getPayload();
if (!(packet instanceof IpV4Packet))
return;
IpV4Packet ipV4Packet = (IpV4Packet) packet;
IpV4Header ipV4Header = ipV4Packet.getHeader();
packet = ipV4Packet.getPayload();
if (!(packet instanceof IcmpV4CommonPacket))
return;
IcmpV4CommonPacket icmpPacket = (IcmpV4CommonPacket) packet;
packet = icmpPacket.getPayload();
// successful reply just measure time and done
if (packet instanceof IcmpV4EchoReplyPacket) {
IcmpV4EchoReplyPacket icmpV4EchoReplyPacket = (IcmpV4EchoReplyPacket) packet;
IcmpV4EchoReplyHeader icmpV4EchoReplyHeader = icmpV4EchoReplyPacket.getHeader();
if (icmpV4EchoReplyHeader.getIdentifier() != identifier)
return;
if (icmpV4EchoReplyHeader.getSequenceNumber() != sequence)
return;
// here you got an echo reply
System.out.println(packet);
return;
}
// try handle time to live exceeded messages
if (packet instanceof IcmpV4TimeExceededPacket) {
packet = packet.getPayload(); // original IPv4
if (!(packet instanceof IpV4Packet))
return;
packet = packet.getPayload(); // original ICMP common
if (!(packet instanceof IcmpV4CommonPacket))
return;
packet = packet.getPayload(); // original ICMP echo
if (!(packet instanceof IcmpV4EchoPacket))
return;
IcmpV4EchoHeader icmpV4EchoHeader = ((IcmpV4EchoPacket)packet).getHeader();
if (icmpV4EchoHeader.getIdentifier() != IDENTIFIER)
return;
if(icmpV4EchoHeader.getSequenceNumber() != SEQUENCE)
return;
// HERE you got an answer, that the time to live has been used up
System.out.println(packet);
return;
}
};
}
组合:
public static void main(String[] args) throws IOException, PcapNativeException, NotOpenException, InterruptedException {
try (PcapHandle handle = PCAP4J_NETWORK_INTERFACE.openLive(1024, PromiscuousMode.PROMISCUOUS, 1000)) {
// set filter to only get incoming ICMP traffic
handle.setFilter("icmp and dst host " + Pcaps.toBpfString(IP_ORIGIN), BpfCompileMode.OPTIMIZE);
// send ARP request
Packet p = buildPacket();
handle.sendPacket(p);
// wait (forever) for ARP answer
PacketListener listener = buildListener();
handle.loop(-1, listener);
}
}