我想实现基本的网络检查功能来测试提供的 url 是否响应(例如ping www.google.com)。它必须提供操作信息,例如,请求的服务不可用或主机无法到达。我可以使用icmp4j库来实现它。但我想使用pcap4j库来实现相同的目标。我想将 url 放在文本框中,然后单击连接按钮,该按钮将调用 pcap4j api 来检查主机是否响应。
2 回答
我花了一年多的时间才弄清楚这一点,因为我想用 pcap4j 做一个 traceroute,所以这就是我所做的:
- 获取您的 IPv4 地址和 Mac 地址,这可以通过查询
PcapNetworkInterface
- 获取目标 IP 地址,如果您有 DNS 名称,则需要事先解析它。
- 获取目标 Mac 地址。
- 目标在同一个子网中:发送一个 ARP 请求来解析 mac(或者,mac 广播也可以正常工作)。
- 目标在不同的子网中:您需要获取网关服务器的mac,这并不容易。假设您有其他网络流量正在进行,您可以侦听传入的数据包并获取源 mac,其中源 IP 地址来自不同的子网,这可能是网关服务器的 mac 地址。
- 创建
IcmpV4EchoPacket
并发送 - 侦听传入的 ICMP 流量,您将获得以下三个之一:
IcmpV4EchoReplyPacket
可能是对您请求的答复(请检查标识符和序列号以确保)- A
IcmpV4TimeExceededPacket
如果在您指定的生存时间内无法达到目标 - 什么都没有,路由器和 ping 的目标可以随意忽略并且不回答您的请求
需要填写的变量:
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 请求数据包(作为 of IcmpV4CommonPacket
ofIpV4Packet
的有效负载EthernetPacket
):
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 Echo 应答或超时的侦听器:
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);
}
}
IcmpV4EchoPacket
您可以使用、IcmpV4CommonPacket
、的构建器在以太网数据包上创建 ICMPv4 Echo (ping)IpV4Packet
并EthernetPacket
通过 发送它们PcapHandle.sendPacket()
。请参考org.pcap4j.sample.SendFragmentedEcho
pcap4j-sample 项目。
您将需要实现 ARP 以将 IP 地址解析为 MAC 地址org.pcap4j.sample.SendArpRequest
,例如 pcap4j-sample 项目中的地址。
您还需要实现一项功能,以某种方式从给定的 IP 地址中找到下一跳(默认网关左右)。Pcap4J 不提供 API 来支持这种实现。(Java 不提供 API 来获取路由表...)
你可能会更好地使用java.net.InetAddress#isReachable()
。