Java實現實時生效hosts文件修改


最近在編寫測試自動化代碼時,由於項目測試需求,需要在代碼中嵌入動態hosts文件修改並使之實時生效。對於hosts文件修改,就是簡單的文件讀和寫,不做細說明(請直接看底部代碼);本文重點說下如何讓動態的hosts文件修改直接生效。

《一》 Java小白幼稚想法被現實扇了一巴掌


說到hosts文件修改后實時生效,在Windows下,手工方式,往往常用以下三種方式:
1. 重啟瀏覽器
2. 在CMD中執行“ipconfig /flushdns”命令(可以不用重啟瀏覽器)
3. 修改注冊表,自動生效,只需頁面刷新即可(如果是手動方式,一次修改即可一勞永逸)
在注冊表中,HKeyCurrentUser\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings下增加:
DnsCacheEnabled 0x0 (REG_DWORD)
DnsCacheTimeout 0x0 (REG_DWORD)
ServerInfoTimeOut 0x0 (REG_DWORD)

鑒於上面的方式,在實現完畢hosts文件修改后,如何使用Java來實現實時生效,泛起的第一個念頭就是:用java直接調用Windows bat文件,可以即上面的提到的第二種方式不就OK了?立馬查資料,發現用來調用DOS命令也挺簡單的,代碼如下:

try {
     Runtime.getRuntime().exec("ipconfig /flushdns");
} catch (IOException e) {
     System.out.println("ERROR: call runtime to flushdns exception: " + e.getMessage());           
}

滿懷開心的,運行下面測試代碼,來進行測試驗證。

public static void main(String[] args) throws IOException, InterruptedException, Exception {
            String testURL = “www.taobao.com”;
            String testHostIP = “10.232.35.181”;
            System.out.println(InetAddress.getByName(testURL));
            // 更新hosts文件,將淘寶首頁域名指向指定的IP
            updateHost(testHostIP,  testURL);
            // 調用DOS命令,進行host生效
    Runtime.getRuntime().exec("ipconfig /flushdns");       
            System.out.println(InetAddress.getByName(testURL);
    }

但嘗試了幾次,結果卻並不如之前預期的那樣?這是要鬧那樣,明明瀏覽器時這樣work得好好的。到JAVA里咋就介么不給力呢~

《二》 萬事問Google,柳暗花明又一村


懊惱沒用啊,趕緊繼續GOOGLE去,一查,原來Java有個東東叫DNS Caching in Java Virtual Machines. 它不像其他大部分的Stand-alone的桌面應用和網絡應用一樣,直接將系統的DNS Flush一下或重啟就可以生效。Jdk為了提升系統性能,通過InetAddress將網絡訪問后的dns解析結果cache起來,並提供了以下方法來查詢hostname和IP的匹配關系。


getAddress Returns the raw IP Address for this object.
getAllByName(String host) Given the name of host, an array of IP address is returned.
getByAddress(byte[] addr) Returns an InetAddress object given the raw IP address
getByAddress(String host, byte[] addr) Create an InetAddress based on the provided host name and IP address
getByName(String host) Determines the IP address of a host, given the host's name.
getCanonicalHostName() Gets the fully qualified domain name for this IP address.
getHostAddress() Returns the IP address string in textual presentation
getHostName() Gets the host name for this IP address
getLocalHost() Returns the local host.

(忍不住吐槽:媽的,說這么多,上面的這個東東同這次的主題“實時生效hosts文件修改“有毛關系)

除了InetAddress可以查詢緩存的信息, Java中用了四個屬性來管理JVM DNS Cache TTL(Time To Live),即DNS Cache的緩存失效時間。(這才有點像是要說到重點了~)

networkaddress.cache.ttl

  • 緩存正確解析后的IP地址
  • 指定的整數表明會緩存正確解析的DNS多長時間
  • 默認值為-1, 代表在JVM啟動期間會一直緩存
  • 如果設置為0,則表示不緩存正確解析的結果
  • 如果不設置,默認緩存30秒

networkaddress.cache.negative.ttl

  • 緩存解析失敗結果,可以減少DNS服務器壓力
  • 指定的整數表明會緩存解析失敗結果多長時間
  • 默認值為10,表示JVM會cache失敗解析結果10秒
  • 如果該值設置為0, 則表示不緩存失敗結果

sun.net.inetaddr.ttl

  • 私有變量,對應networkaddress.cache.ttl
  • 這個參數,只能在命令行中被設置值。

sun.net.inetaddr.negative.ttl

  • 私有變量,對應networkaddress.cache.negative.ttl,
  • 同樣,只能在命令行中被設置值。

 

《三》 不容易啊,終於切入正題


使用DNS Caching in Java Virtual Machines文中提到的方式,我們可以通過以下三種方式來進行對host修改后的實時生效:

  • 編輯$JAVA_HOME/jre/lib/secerity/java.security文件中將網絡地址緩存屬性(networkaddress.cache.ttl和networkaddress.cache.negative.ttl)的值修改為你想要的值;優點是一勞永逸性的修改,非編程式的解決方案; 但java.security是公用資源文件,這個方式會影響這台機器上所有的JVM

  • 在代碼中可直接將動態配置,方式如下:
    java.security.Security.setProperty(“propertyname”, “value”)
    好處時,只影響當前的JVM,不影響他人,但缺點是,它是編程式的,
    但正是利用了這一點,讓我們的host文件修改可以實時生效

    舉個例子:

Security.setProperty("networkaddress.cache.ttl", "0");
Security.setProperty("networkaddress.cache. negative .ttl", "0");

 

  • 在JVM啟動時,在命令行中加入-Dsun.net.inetaddr.ttl=value and -Dsun.net.inetaddr.negative.ttl=value這兩個指令,也可以起到配置緩存DNS失效時間作用。但注意這個方式,只在當networkaddress.cache.*屬性沒有配置時才能起作用。

  • 除了上面提到的問題,我還找到一個資料,是通過Java反射機制,將InetAddresss類中的static變量addressCache強行修改方式,來達到實時生效hosts文件修改的目的。這種方式同上面提到的修改Java DNS Cache TTL不同,是不會去修改Cache配置,而是將運行中的緩存的IP/hostname對應關系數據強制修改。經過測試驗證,確實可行。

public static void modifyDnsCachePolicy(String hostname) throws Exception {
         // 開始修改緩存數據
         Class inetAddressClass = InetAddress.class;
         final Field cacheField = inetAddressClass.getDeclaredField("addressCache");
         cacheField.setAccessible(true);
         final Object obj = cacheField.get(inetAddressClass);
         Class cacheClazz = obj.getClass();
         final Field cacheMapField = cacheClazz.getDeclaredField("cache");
         cacheMapField.setAccessible(true);
         final Map cacheMap = (Map) cacheMapField.get(obj);
         cacheMap.remove(hostname);
         // 修改緩存數據結束
    }

 


《四》 看完第三部分,你現在可以鄙視我,這個東東居然也會跌坑里。嘿嘿,不過問題總算解決了。還有些收獲


  • 下面是這次寫的實現hosts文件修改的實現代碼,支持功能有:
    • 不破壞原有hosts文件,支持新host綁定或修改
    • 支持host解綁
    • Technorati 標簽: Java, DNS Cache, Hosts

/**
         * 獲取host文件路徑
         * @return
         */
        public static String getHostFile() {
            String fileName = null;
            // 判斷系統
            if ("linux".equalsIgnoreCase(System.getProperty("os.name"))) {
                fileName = "/etc/hosts";
            } else {
                fileName = System.getenv("windir") + "\\system32\\drivers\\etc\\hosts";
            }
            return fileName;
        }
        /**
         * 根據輸入IP和Domain,刪除host文件中的某個host配置
         * @param ip
         * @param domain
         * @return
         */
        public synchronized static boolean deleteHost(String ip, String domain) {
            if (ip == null || ip.trim().isEmpty() || domain == null || domain.trim().isEmpty()) {
                throw new IllegalArgumentException("ERROR: ip & domain must be specified");
            }
            String splitter = " ";
            /**
             * Step1: 獲取host文件
             */
            String fileName = getHostFile();
            List<?> hostFileDataLines = null;
            try {
                hostFileDataLines = FileUtils.readLines(new File(fileName));
            } catch (IOException e) {
                System.out.println("Reading host file occurs error: " + e.getMessage());
                return false;
            }
            /**
             * Step2: 解析host文件,如果指定域名不存在,則Ignore,如果已經存在,則直接刪除該行配置
             */
            List<String> newLinesList = new ArrayList<String>();
            // 標識本次文件是否有更新,比如如果指定的IP和域名已經在host文件中存在,則不用再寫文件
            boolean updateFlag = false;
            for (Object line : hostFileDataLines) {
                String strLine = line.toString();
                // 將host文件中的空行或無效行,直接去掉
                if (StringUtils.isEmpty(strLine) || strLine.trim().equals("#")) {
                    continue;
                }
                // 如果沒有被注釋掉,則
                if (!strLine.trim().startsWith("#")) {
                    strLine = strLine.replaceAll("", splitter);
                    int index = strLine.toLowerCase().indexOf(domain.toLowerCase());
                    // 如果行字符可以匹配上指定域名,則針對該行做操作
                    if (index != -1) {
                        // 匹配到相同的域名,直接將整行數據干掉
                        updateFlag = true;
                        continue;
                    }
                }
                // 如果沒有匹配到,直接將當前行加入代碼中
                newLinesList.add(strLine);
            }
            /**
             * Step3: 將更新寫入host文件中去
             */
            if (updateFlag) {
                try {
                    FileUtils.writeLines(new File(fileName), newLinesList);
                } catch (IOException e) {
                    System.out.println("Updating host file occurs error: " + e.getMessage());
                    return false;
                }
            }
            return true;
        }
        /**
         * 根據輸入IP和Domain,更新host文件中的某個host配置
         * @param ip
         * @param domain
         * @return
         */
        public synchronized static boolean updateHost(String ip, String domain) {
            // Security.setProperty("networkaddress.cache.ttl", "0");
            // Security.setProperty("networkaddress.cache.negative.ttl", "0");
            if (ip == null || ip.trim().isEmpty() || domain == null || domain.trim().isEmpty()) {
                throw new IllegalArgumentException("ERROR: ip & domain must be specified");
            }
            String splitter = " ";
            /**
             * Step1: 獲取host文件
             */
            String fileName = getHostFile();
            List<?> hostFileDataLines = null;
            try {
                hostFileDataLines = FileUtils.readLines(new File(fileName));
            } catch (IOException e) {
                System.out.println("Reading host file occurs error: " + e.getMessage());
                return false;
            }
            /**
             * Step2: 解析host文件,如果指定域名不存在,則追加,如果已經存在,則修改IP進行保存
             */
            List<String> newLinesList = new ArrayList<String>();
            // 指定domain是否存在,如果存在,則不追加
            boolean findFlag = false;
            // 標識本次文件是否有更新,比如如果指定的IP和域名已經在host文件中存在,則不用再寫文件
            boolean updateFlag = false;
            for (Object line : hostFileDataLines) {
                String strLine = line.toString();
                // 將host文件中的空行或無效行,直接去掉
                if (StringUtils.isEmpty(strLine) || strLine.trim().equals("#")) {
                    continue;
                }
                if (!strLine.startsWith("#")) {
                    strLine = strLine.replaceAll("", splitter);
                    int index = strLine.toLowerCase().indexOf(domain.toLowerCase());
                    // 如果行字符可以匹配上指定域名,則針對該行做操作
                    if (index != -1) {
                        // 如果之前已經找到過一條,則說明當前line的域名已重復,
                        // 故刪除當前line, 不將該條數據放到newLinesList中去
                        if (findFlag) {
                            updateFlag = true;
                            continue;
                        }
                        // 不然,則繼續尋找
                        String[] array = strLine.trim().split(splitter);
                        Boolean isMatch = false;
                        for (int i = 1; i < array.length; i++) {
                            if (domain.equalsIgnoreCase(array[i]) == false) {
                                continue;
                            } else {
                                findFlag = true;
                                isMatch = true;
                                // IP相同,則不更新該條數據,直接將數據放到newLinesList中去
                                if (array[0].equals(ip) == false) {
                                    // IP不同,將匹配上的domain的ip 更新成設定好的IP地址
                                    StringBuilder sb = new StringBuilder();
                                    sb.append(ip);
                                    for (int j = 1; i < array.length; i++) {
                                        sb.append(splitter).append(array[j]);
                                    }
                                    strLine = sb.toString();
                                    updateFlag = true;
                                }
                            }
                        }
                    }
                }
                // 如果有更新,則會直接更新到strLine中去
                // 故這里直接將strLine賦值給newLinesList
                newLinesList.add(strLine);
            }
            /**
             * Step3: 如果沒有任何Host域名匹配上,則追加
             */
            if (!findFlag) {
                newLinesList.add(new StringBuilder(ip).append(splitter).append(domain).toString());
            }
            /**
             * Step4: 不管三七二十一,寫設定文件
             */
            if (updateFlag || !findFlag) {
                try {
                    FileUtils.writeLines(new File(fileName), newLinesList);
                } catch (IOException e) {
                    System.out.println("Updating host file occurs error: " + e.getMessage());
                    return false;
                }
            }
            return true;
        }

 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM