NiceLeeのBlog 用爱发电 bilibili~

Java 如何简便地实现host更改功能

2020-06-13
nIceLee

阅读:


一直以来,指定解析某些特定域名为特定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);
	}
}

内容
隐藏