环境:
- FritzBox 7530 v7.29
- 将端口 7、9、80 和 433 IPv4 和 IPv6 转发到 HomeServer
- CentOS Stream 8 HomeServer 通过以太网电缆直接连接到 FritzBox
- 拥有公共 IPv6 地址,与其他 ISP 客户共享 IPv4 地址
- IPv6 DynDNS 服务正确更新 IPv6 前缀
- Java脚本发送魔术包
- 转发功能有效,当 HomeServer 运行时,Apache 和其他服务可访问
- 需要能够通过 WakeOnLan 远程启动 HomeServer
问题: 为什么以下脚本仅偶尔通过 DynDNS 域或 IPv6 地址启动 HomeServer?只有本地广播 IPv4 每次都有效。
脚本:
import java.net.*;
public class WakeOnLan {
public static final int PORT = 9;
public static void main(String[] args) {
// works every time
// local broadcast works every time
String ipStr = "192.168.178.255";
// works only sometimes
// local link adress works only sometimes
String ipStr = "fe80::IPv6 mac address of target";
// works only sometimes
// public IPv6 prefix with IPv6 mac address of target
String ipStr = "public IPv6 prefix:IPv6 mac address of target";
// mac adress of target
String macStr = "mac address of target";
try {
byte[] macBytes = getMacBytes(macStr);
byte[] bytes = new byte[6 + 16 * macBytes.length];
for (int i = 0; i < 6; i++) {
bytes[i] = (byte) 0xff;
}
for (int i = 6; i < bytes.length; i += macBytes.length) {
System.arraycopy(macBytes, 0, bytes, i, macBytes.length);
}
InetAddress address = InetAddress.getByName(ipStr);
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, PORT);
DatagramSocket socket = new DatagramSocket();
socket.send(packet);
socket.close();
System.out.println("Wake-on-LAN packet sent to: "+address);
}
catch (Exception e) {
System.out.println("Failed to send Wake-on-LAN packet:"+e);
System.exit(1);
}
}
private static byte[] getMacBytes(String macStr) throws IllegalArgumentException {
byte[] bytes = new byte[6];
String[] hex = macStr.split("(\\:|\\-)");
if (hex.length != 6) {
throw new IllegalArgumentException("Invalid MAC address.");
}
try {
for (int i = 0; i < 6; i++) {
bytes[i] = (byte) Integer.parseInt(hex[i], 16);
}
}
catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid hex digit in MAC address.");
}
return bytes;
}
}
答案1
问题:为什么以下脚本仅偶尔通过 DynDNS 域或 IPv6 地址启动 HomeServer?只有本地广播 IPv4 每次都有效。
因为如果源主机(或路由器)不知道要将数据包放在哪个目标 MAC 地址上,它就无法将数据包传送到单播地址。要确定目标第 2 层地址,它必须发出 ARP 或邻居发现查询,您的家庭服务器必须对此作出响应 - 而它在睡眠状态下无法执行此操作。
如果在家庭服务器仍处于通电状态时进行 ARP 查询,则 WoL 将继续工作几分钟,而学习到的 MAC 地址位于您的邻居/ARP 缓存中,但是缓存条目在几分钟不使用后就会过期。
(话虽如此,我听说有些 NIC 具有 ARP 和 ND 卸载功能,可以代表睡眠主机回答 ARP 查询,但我不认为这是一个常见的功能。)
无论您使用 IPv4 端口转发到特定目的地,还是使用 IPv6,情况都是一样。IPv6 地址的最后 64 位不是自动用作 MAC 地址,并且不会进入第 2 层报头——它们仅仅是一个唯一标识符,通常(但并非总是)源自MAC 地址,但不避免对 ARP/NDP 的需求。
同时,WoL 广播 IP 地址之所以有效,是因为它们始终使用固定的目标 MAC – 例如,255.255.255.255
始终使用ff:ff:ff:ff:ff:ff
,无需为此进行 ARP 查询。(而且 WoL 固件不关心数据包头,它只关注魔术有效载荷。)
IPv6 上没有远程指挥广播,因此如果192.168.178.255
距离有多个路由器,则无法轻松地使用 IPv6 复制它。
因此,为了完全远程使用局域网唤醒(无论是通过 IPv4 端口转发还是通过直接 IPv6),您需要向路由器添加静态邻居缓存条目。要么为服务器的真实 IP+MAC 组合添加一个普通的静态条目,或者保留一个单独的 IP 地址并为其分配 ff:ff:ff:ff:ff:ff 作为 MAC 地址,以便发送给它的所有内容都会被广播。
但是,拥有一个始终在线的主机(例如,您能找到的最便宜的 Pi 等效主机)并远程通过 SSH 连接到该主机以运行局域网唤醒工具会更容易。(某些路由器有局域网唤醒小程序,例如/tool wol
在 RouterOS 上。)
现在,如果你可以在与家庭服务器相同的子网中运行 Java 程序,则 IPv6 确实支持当地的广播 – 相当于255.255.255.255
地址ff02::1
,即“所有节点”多播组。请注意,这是链路范围的,因此您必须在其后加上或填写 sin6_scope_id 字段。%zoneidx
String ipStr = "255.255.255.255";
String ipStr = "ff02::1%eth0";
String ipStr = "ff02::1%wlan0";
另一个选择可能是使用模式匹配时唤醒(而不是魔术包)——许多网卡可以用一些额外的模式来编程,这些模式将触发唤醒,例如,如果在 Windows 中启用了唤醒模式,它将对网卡进行编程,以便实际ARP 查询本身会触发系统唤醒。因此您甚至不需要发送魔术包,只需 ping 主机或向其发送任何 UDP 垃圾即可。(问题是任何 ping 您的主机的人都会唤醒它。)