모 오픈소스를 실행시키다가 이상한 일이 생겨서, 버그인가 하고 보다가… 재미난 현상을 발견했습니다. Java.net.InetAddress 가 뭔가 이상한 결과를 넘겨주는 것입니다. 먼저… 간단한 소스를 보시죠. 테스트 프로그램은 다음과 같습니다.(결론부터 말하자면… 자바의 버그라고 할 수는 없습니다. ㅋㅋㅋ, DNS 변경으로 일단 원하는 결과가 나오는 ㅎㅎㅎ)
* 결론적으로는 U+등이 디지털 네임스랑 계약을 맺고, 분석이 되지 않는 도메인을 디지털네임스로 질의하고, 이를 디지털 네임스에서 키워드로 등록되었거나, 등록되지 않은 주소를 자신의 ip등으로 돌려줘서 발생하는 이슈로 추측되고 있습니다.)
import java.io.*; import java.util.*; import java.net.InetAddress; import java.net.UnknownHostException; public class Test { public static void main(String [] args) { try { System.out.println(InetAddress.getLocalHost()); } catch(UnknownHostException var1) { System.out.println("Exception : " + var1); } } }
그런데 그 결과가 다음과 같습니다. -_-
charsyam ~/works/test $ java Test charsyam.local/218.38.137.28 charsyam ~/works/test $ java Test charsyam.local/218.38.137.28 charsyam ~/works/test $ java Test charsyam.local/192.168.1.7 charsyam ~/works/test $ java Test charsyam.local/192.168.1.7
네, getLocalHost()를 호출한 결과가 218.38.137.28 이거나 192.168.1.7이 나옵니다. 실제 저희 집의 네트웍은 공유기 밑에 접속이 되는 것이라, 192.168.1.7이 기대한 값입니다. 혹시나 외부 아이피인가 해서 확인해도 제 공유기가 가진 아이피도, 위의 218.38.137.28 값은 아니었습니다. 전혀 상관 없는 값이죠.
그런데 재미있는 것은 이 것은 단순히 자바의 문제는 아니라는 것입니다.
dig/nslookup 으로 해본 결과입니다.
nslookup 결과
charsyam ~ $ nslookup Macintosh-7.local Server: 192.168.1.1 Address: 192.168.1.1#53 Name: Macintosh-7.local Address: 218.38.137.28
dig 결과
charsyam ~ $ dig Macintosh-7.local ; <<>> DiG 9.8.3-P1 <<>> Macintosh-7.local ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40380 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;Macintosh-7.local. IN A ;; ANSWER SECTION: Macintosh-7.local. 3600 IN A 218.38.137.28 ;; Query time: 5 msec ;; SERVER: 192.168.1.1#53(192.168.1.1) ;; WHEN: Sun Dec 28 23:44:09 2014 ;; MSG SIZE rcvd: 51
네 DNS 결과가 이상합니다. 흑흑흑, 일단 네임서버가 뭔가 이상한 결과를 던져주는 건 일단 확실한데… 제가 보기엔 여기에 설정된 네임서버가 이상하고, 여기서 질의도 이상한데, 거기에 대해서 이상한 결과를 주는 듯 합니다.
그럼 왜 위의 getLocalHost()의 결과가 저럴까요? 이름만 보면 localhost를 줘야 할 것 같은데… 그게 아닙니다.
이제 Java 소스를 까보도록 하겠습니다. 저부분만…
코드를 해석하면 처음에 LocalHostName을 가져옵니다. 저의 경우는 Macintosh-7.local 이겠죠.
그리고 위의 값이 localhost면 그냥 루프백 주소를 줍니다. 즉 이러면 127.0.0.1 이나 정상적인 값이 갈듯 합니다.
그리고 그 호스트네임을 이용해서 InetAddress.getAddressesFromNameService 을 이용해서 InetAddress를 가져오는데, 여기서 뭔가 해당 도메인 파싱이 잘못되고, 도메인 네임서버로 가서 이상한 결과가 오는게 아닌가 싶습니다.
public static InetAddress getLocalHost() throws UnknownHostException { SecurityManager security = System.getSecurityManager(); try { String local = impl.getLocalHostName(); if (security != null) { security.checkConnect(local, -1); } if (local.equals("localhost")) { return impl.loopbackAddress(); } InetAddress ret = null; synchronized (cacheLock) { long now = System.currentTimeMillis(); if (cachedLocalHost != null) { if ((now - cacheTime) < maxCacheTime) // Less than 5s old? ret = cachedLocalHost; else cachedLocalHost = null; } // we are calling getAddressesFromNameService directly // to avoid getting localHost from cache if (ret == null) { InetAddress[] localAddrs; try { localAddrs = InetAddress.getAddressesFromNameService(local, null); } catch (UnknownHostException uhe) { // Rethrow with a more informative error message. UnknownHostException uhe2 = new UnknownHostException(local + ": " + uhe.getMessage()); uhe2.initCause(uhe); throw uhe2; } cachedLocalHost = localAddrs[0]; cacheTime = now; ret = localAddrs[0]; } } return ret; } catch (java.lang.SecurityException e) { return impl.loopbackAddress(); } }
참고로 getAddressesFromNameService 는 다음과 같이 DNS 프로바인더를 이용해서 실제 DNS쿼리를 하게 됩니다.
private static InetAddress[] getAddressesFromNameService(String host, InetAddress reqAddr) throws UnknownHostException { InetAddress[] addresses = null; boolean success = false; UnknownHostException ex = null; // Check whether the host is in the lookupTable. // 1) If the host isn't in the lookupTable when // checkLookupTable() is called, checkLookupTable() // would add the host in the lookupTable and // return null. So we will do the lookup. // 2) If the host is in the lookupTable when // checkLookupTable() is called, the current thread // would be blocked until the host is removed // from the lookupTable. Then this thread // should try to look up the addressCache. // i) if it found the addresses in the // addressCache, checkLookupTable() would // return the addresses. // ii) if it didn't find the addresses in the // addressCache for any reason, // it should add the host in the // lookupTable and return null so the // following code would do a lookup itself. if ((addresses = checkLookupTable(host)) == null) { try { // This is the first thread which looks up the addresses // this host or the cache entry for this host has been // expired so this thread should do the lookup. for (NameService nameService : nameServices) { try { /* * Do not put the call to lookup() inside the * constructor. if you do you will still be * allocating space when the lookup fails. */ addresses = nameService.lookupAllHostAddr(host); success = true; break; } catch (UnknownHostException uhe) { if (host.equalsIgnoreCase("localhost")) { InetAddress[] local = new InetAddress[] { impl.loopbackAddress() }; addresses = local; success = true; break; } else { addresses = unknown_array; success = false; ex = uhe; } } } // More to do? if (reqAddr != null && addresses.length > 1 && !addresses[0].equals(reqAddr)) { // Find it? int i = 1; for (; i < addresses.length; i++) { if (addresses[i].equals(reqAddr)) { break; } } // Rotate if (i < addresses.length) { InetAddress tmp, tmp2 = reqAddr; for (int j = 0; j < i; j++) { tmp = addresses[j]; addresses[j] = tmp2; tmp2 = tmp; } addresses[i] = tmp2; } } // Cache the address. cacheAddresses(host, addresses, success); if (!success && ex != null) throw ex; } finally { // Delete host from the lookupTable and notify // all threads waiting on the lookupTable monitor. updateLookupTable(host); } } return addresses; }