【Java踩坑記】——一個關於dns修改的需求

目錄

問題描述

方案查找

解決問題


  • 問題描述

前些天遇到一個需求,我們需要修改本地的dns解析,去驗證業務的正確性,想到的第一個方案就是修改本地的hosts文件。但是後來考量到這樣需要頻繁的修改本地磁盤文件。於是開始尋覓其他的方案。

  • 方案查找

經過一番持續不斷的百度^_^,查找到一個工具包(https://github.com/tanhaichao/javahost)然後查看了一下他的核心代碼

public class DnsImpl extends AbstractDns {
    //省略代碼
    ……

    public boolean update(String host, String ip) {
        return this.update(host, new String[]{ip});
    }

    public boolean update(String host, String[] ips) {
        Object entry = this.createCacheEntry(host, ips);
        this.getAddressCache().put(host, entry);
        return true;
    }

   
    //省略代碼
    ……
   
}
public abstract class AbstractDns implements Dns {
   
    // 省略代碼
    ……

    protected Map<String, Object> getAddressCache() {
        try {
            Field cacheField = InetAddress.class.getDeclaredField("addressCache");
            cacheField.setAccessible(true);
            Object addressCache = cacheField.get(InetAddress.class);
            Class clazz = addressCache.getClass();
            Field cacheMapField = clazz.getDeclaredField("cache");
            cacheMapField.setAccessible(true);
            return (Map)cacheMapField.get(addressCache);
        } catch (Exception var5) {
            throw new RuntimeException(var5.getMessage(), var5);
        }
    }

   
    protected Object createCacheEntry(String host, String[] ips) {
        try {
            long expiration = System.currentTimeMillis() + 315360000000L;
            InetAddress[] addresses = new InetAddress[ips.length];

            for(int i = 0; i < addresses.length; ++i) {
                addresses[i] = InetAddress.getByAddress(host, InetAddress.getByName(ips[i]).getAddress());
            }

            String className = "java.net.InetAddress$CacheEntry";
            Class<?> clazz = Class.forName(className);
            Constructor<?> constructor = clazz.getDeclaredConstructors()[0];
            constructor.setAccessible(true);
            return constructor.newInstance(addresses, expiration);
        } catch (Exception var9) {
            throw new RuntimeException(var9.getMessage(), var9);
        }
    }


    // 省略代碼
    ……
}

可以看到,這個工具類實際是通過反射機制,去修改了InetAddress中的cache值,來實現dns解析的修改。

  • 解決問題

在解決問題的過程中,當使用CloseableHttpClient發起get請求的時候,能夠正常使用。分析具體原因如下:


public class DefaultHttpClientConnectionOperator implements HttpClientConnectionOperator {
    
    //省略代碼
    ……

    public void connect(ManagedHttpClientConnection conn, HttpHost host, InetSocketAddress localAddress, int connectTimeout, SocketConfig socketConfig, HttpContext context) throws IOException {
        Lookup<ConnectionSocketFactory> registry = this.getSocketFactoryRegistry(context);
        ConnectionSocketFactory sf = (ConnectionSocketFactory)registry.lookup(host.getSchemeName());
        if (sf == null) {
            throw new UnsupportedSchemeException(host.getSchemeName() + " protocol is not supported");
        } else {
            InetAddress[] addresses = host.getAddress() != null ? new InetAddress[]{host.getAddress()} : this.dnsResolver.resolve(host.getHostName());
            int port = this.schemePortResolver.resolve(host);

            for(int i = 0; i < addresses.length; ++i) {
                InetAddress address = addresses[i];
                boolean last = i == addresses.length - 1;
                Socket sock = sf.createSocket(context);
                sock.setSoTimeout(socketConfig.getSoTimeout());
                sock.setReuseAddress(socketConfig.isSoReuseAddress());
                sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
                sock.setKeepAlive(socketConfig.isSoKeepAlive());
                if (socketConfig.getRcvBufSize() > 0) {
                    sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
                }

                if (socketConfig.getSndBufSize() > 0) {
                    sock.setSendBufferSize(socketConfig.getSndBufSize());
                }

                int linger = socketConfig.getSoLinger();
                if (linger >= 0) {
                    sock.setSoLinger(true, linger);
                }

                conn.bind(sock);
                InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
                if (this.log.isDebugEnabled()) {
                    this.log.debug("Connecting to " + remoteAddress);
                }

                try {
                    sock = sf.connectSocket(connectTimeout, sock, host, remoteAddress, localAddress, context);
                    conn.bind(sock);
                    if (this.log.isDebugEnabled()) {
                        this.log.debug("Connection established " + conn);
                    }

                    return;
                } catch (SocketTimeoutException var19) {
                    if (last) {
                        throw new ConnectTimeoutException(var19, host, addresses);
                    }
                } catch (ConnectException var20) {
                    if (last) {
                        String msg = var20.getMessage();
                        if ("Connection timed out".equals(msg)) {
                            throw new ConnectTimeoutException(var20, host, addresses);
                        }

                        throw new HttpHostConnectException(var20, host, addresses);
                    }
                } catch (NoRouteToHostException var21) {
                    if (last) {
                        throw var21;
                    }
                }

                if (this.log.isDebugEnabled()) {
                    this.log.debug("Connect to " + remoteAddress + " timed out. " + "Connection will be retried using another IP address");
                }
            }

        }
    }

   
}

在上述關鍵代碼中,我們看到CloseableHttpClient方法在做connect的時候,利用下述方法先獲取DNS數據。

InetAddress[] addresses = host.getAddress() != null ? new InetAddress[]{host.getAddress()} : this.dnsResolver.resolve(host.getHostName());

this.dnsResolver.resolve(host.getHostName())跟蹤該方法,我們能夠看到它是根據InetAddress.getAllByName獲取到相應的DNS解析記錄,然後根據dns解析去建立socket鏈接。

 public InetAddress[] resolve(String host) throws UnknownHostException {
        return InetAddress.getAllByName(host);
 }

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章