一直以来,指定解析某些特定域名为特定IP,要实现这个功能,我一直的思路是搞个代理。
HTTP代理倒是无所谓,反正是明文传输,拿到什么转什么,将代理直接设为要绑定的IP即可。
但是HTTPS请求不行,多了CONNECT
消息建立隧道这一步,于是乎只能自己建立一个代理。
这样效率实际上是非常低下的。
那么有什么更好的办法呢?
最近偶然碰到了一个功能实现,让系统的host的修改在现有的java应用中立即生效,于是有了新的想法。
实现思路
在java.net.InetAddress
类下,维护了一个静态的addressCache
,DNS查询基本上先从缓存里面找,找不到再网络查询。
刷新DNS即是清空cache。
那么,我们可不可以直接从过反射来操作,使得在Java程序内部自定义Host功能得以实现呢?
初步实现
这里直接调用InetAddress$Cache
的put方法,该条DNS记录的有效期为默认有效期(30s)。
为了保证一直是劫持的DNS,保险的做法是每次网络请求之前先调用一遍。
但是这样很麻烦。
public class HostSet {
public static void main(String[] args) {
try {
Field field = InetAddress.class.getDeclaredField("addressCache");
field.setAccessible(true);
Object addressCache = field.get(null);
Method method = addressCache.getClass().getMethod("put", String.class, InetAddress[].class);
method.setAccessible(true);
InetAddress[] ipAddr = new InetAddress[1];
ipAddr[0] = getInetAddr("127.0.0.1");
synchronized (addressCache) {
method.invoke(addressCache, "www.baidu.com", ipAddr);
}
// 测试
InetAddress addresses = InetAddress.getByName("www.baidu.com");
System.out.println(addresses.getHostAddress());
// 还原可见性
method.setAccessible(true);
field.setAccessible(false);
} catch (Exception e) {
e.printStackTrace();
}
}
static InetAddress getInetAddr(String ip) throws UnknownHostException {
String[] ipStr = ip.split("\\.");
byte[] ipBuf = new byte[4];
for (int i = 0; i < 4; i++) {
ipBuf[i] = (byte) (Integer.parseInt(ipStr[i]) & 0xff);
}
return InetAddress.getByAddress(ipBuf);
}
}
进一步优化
深入了解InetAddress$Cache
的put方法,发现跟一个Hashmap有关联。
仔细阅读,只需要将过期时间设置为-1即可永久生效。
public class Test {
public static void main(String[] args) {
try {
Field field = InetAddress.class.getDeclaredField("addressCache");
field.setAccessible(true);
Object addressCache = field.get(null);
// 获取Map
Field cacheMapField = addressCache.getClass().getDeclaredField("cache");
cacheMapField.setAccessible(true);
Map cacheMap = (Map) cacheMapField.get(addressCache);
// 获取CacheEntry(内部类) Map 的 Value
Class<?> cls = Class.forName("java.net.InetAddress$CacheEntry");
Constructor<?> constructor = cls.getDeclaredConstructor(InetAddress[].class, long.class);
constructor.setAccessible(true);
InetAddress[] ipAddr = new InetAddress[1];
ipAddr[0] = getInetAddr("127.0.0.1");
Object value = constructor.newInstance(ipAddr, -1);
// 设置host(永不过期)
synchronized (addressCache) {
cacheMap.put("www.baidu.com", value);
}
// 测试
InetAddress addresses = InetAddress.getByName("www.baidu.com");
System.out.println(addresses.getHostAddress());
// 还原可见性
constructor.setAccessible(false);
field.setAccessible(false);
} catch (Exception e) {
e.printStackTrace();
}
}
static InetAddress getInetAddr(String ip) throws UnknownHostException {
String[] ipStr = ip.split("\\.");
byte[] ipBuf = new byte[4];
for (int i = 0; i < 4; i++) {
ipBuf[i] = (byte) (Integer.parseInt(ipStr[i]) & 0xff);
}
return InetAddress.getByAddress(ipBuf);
}
}