성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>존재하지 않는 IP 주소에 대한 Dns.GetHostByAddress/gethostbyaddr/GetNameInfoW 실행이 느리다면?</h1> <p> 간단한 재현 코드는 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Diagnostics; using System.Net; class Program { static void Main(string[] args) { var dt = DateTime.Now; Console.WriteLine(dt); IPHostEntry entry = null; try { entry = System.Net.Dns.GetHostByAddress("192.168.102.5"); // 존재하지 않는 IP 주소 } catch { } var elapsed = DateTime.Now - dt; Console.WriteLine(DateTime.Now + ", " + elapsed.TotalMilliseconds + ", " + entry); } } /* 출력 결과 2019-03-20 오후 2:45:41 2019-03-20 오후 2:45:46, 4549.9928, */ </pre> <br /> (처음 실행 시의 GC 타임을 감안해도) 4.5초가 걸린다니, 만약 웹 애플리케이션에서 이것을 사용하면 장애 발생과 다를 바 없는 상황입니다. 도대체 왜 이런 결과가 나오는 걸까요?<br /> <br /> <hr style='width: 50%' /><br /> <br /> 원인을 밝히기 위해 드디어 ^^ netsh trace를 써먹을 때가 되었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Netsh의 네트워크 모니터링 기능 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11801'>http://www.sysnet.pe.kr/2/0/11801</a> </pre> <br /> 위의 예제 코드를 다음의 batch 스크립트에 끼워서 실행시키고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > REM trace_net.bat REM 관리자 권한으로 실행 netsh trace start capture=YES report=YES persistent=YES ConsoleApp1.exe netsh trace stop </pre> <br /> %LOCALAPPDATA%\Temp\NetTraces 폴더에 생성된 etl 파일을 Network Monitor 프로그램으로 열어 보면, 패킷 캡처된 것들 중에 DNS Resolve와 관련된 세션을 2개 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > - All Traffic - Other Traffic - NetEvent ActivityID 1 - NDIS-PacketCapture SystemTrace - NDISPacCap_MicrosoftWindowsNDISPacketCapture ...[생략]... + IPv4 (192.168.100.25 - <span style='color: blue; font-weight: bold'>164.124.101.2</span>) ConvID = 18 + IPv4 (192.168.100.25 - <span style='color: blue; font-weight: bold'>192.168.102.5</span>) ConvID = 21 ...[생략]... </pre> <br /> "ConvID = 18" 세션의 상세 보기를 하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 198 ... 192.168.100.25 164.124.101.2 DNS DNS:QueryId = 0x43BD, QUERY (Standard query), Query for 5.102.168.192.in-addr.arpa of type PTR on class Internet 199 ... 192.168.100.25 164.124.101.2 DNS DNS:QueryId = 0x43BD, QUERY (Standard query), Query for 5.102.168.192.in-addr.arpa of type PTR on class Internet 200 ... 164.124.101.2 192.168.100.25 DNS DNS:QueryId = 0x43BD, QUERY (Standard query), Response - Name Error 201 ... 164.124.101.2 192.168.100.25 DNS DNS:QueryId = 0x43BD, QUERY (Standard query), Response - Name Error </pre> <br /> 제 컴퓨터의 DNS 서버(164.124.101.2)로 IP 주소에 대한 이름 풀이를 위해 쿼리를 전송하고 응답으로 "Response - Name Error"를 받고 있습니다. 당연히 없는 컴퓨터의 IP 주소이므로 PTR 레코드에 따른 reverse DNS Lookup에 실패하는 것입니다. 그렇다면 그다음 나오는 "Conv = 21"은 뭘까요? 역시 상세 정보 보기로 하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 202 ... 192.168.102.5 <span style='color: blue; font-weight: bold'>NbtNs</span> NbtNs:Query Request for *<00><00><00><00><00><00><00><00><00><00><00><00><00><00><00> <0x00> Workstation Service 203 ... 192.168.102.5 NbtNs NbtNs:Query Request for *<00><00><00><00><00><00><00><00><00><00><00><00><00><00><00> <0x00> Workstation Service 1630 ... 192.168.102.5 NbtNs NbtNs:Query Request for *<00><00><00><00><00><00><00><00><00><00><00><00><00><00><00> <0x00> Workstation Service 1631 ... 192.168.102.5 NbtNs NbtNs:Query Request for *<00><00><00><00><00><00><00><00><00><00><00><00><00><00><00> <0x00> Workstation Service 3259 ... 192.168.102.5 NbtNs NbtNs:Query Request for *<00><00><00><00><00><00><00><00><00><00><00><00><00><00><00> <0x00> Workstation Service 3260 ... 192.168.102.5 NbtNs NbtNs:Query Request for *<00><00><00><00><00><00><00><00><00><00><00><00><00><00><00> <0x00> Workstation Service </pre> <br /> NbtNs 프로토콜로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > NbtNs ; <a target='tab' href='http://www.pciqsatalk.com/2016/03/disable-lmnr-netbios.html'>http://www.pciqsatalk.com/2016/03/disable-lmnr-netbios.html</a> NetBIOS Name Service (NBTNS) - The NBTNS protocol is basically the same thing as LLMNR but only works on IPv4 hosts and is most associated with Windows XP machines. </pre> <br /> 다시 한번 존재하지 않는 IP로 이름 풀이를 시도하고 있습니다. 그렇습니다. 바로 저 통신이 문제의 지연 현상을 일으키고 있는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그렇다면 이제 문제 해결을 할 수 있게 되었습니다. NetBIOS 서비스를 사용하지 않도록 설정하면 되므로 각각의 네트워크 어댑터 설정에 가서 다음과 같이 "Disable NetBIOS over TCP/IP" 설정을 선택하면 됩니다.<br /> <br /> <img alt='dns_gethostbyaddress_2.jpg' src='/SysWebRes/bbs/dns_gethostbyaddress_2.jpg' /><br /> <br /> 그런데, 여기서 한 가지 문제가 있습니다. 만약 자신의 컴퓨터에 네트워크 어댑터가 논리적이든, 물리적이든 여러 개가 있다면 "Internet Protocol Version 4 (TCP/IPv4)"에 체크된 모든 어댑터에 대해서 저 설정을 해야 합니다. 가령 제 컴퓨터에서는 다음과 같이 좌측에 있는 5개 중에 3개의 어댑터가 "Internet Protocol Version 4 (TCP/IPv4)" 설정을 가지고 있어서 그 3개 모두 "Disable NetBIOS over TCP/IP" 설정을 해야만 했습니다.<br /> <br /> <img alt='dns_gethostbyaddress_3.png' src='/SysWebRes/bbs/dns_gethostbyaddress_3.png' /><br /> <br /> 아마도 현업에서라면 이 작업을 쉽게 할 수 있도록 프로그램을 만들거나, 다음의 글에 나오는 vbs 스크립트를 수정해 사용하면 될 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Part 6: Scripting WINS on Clients ; <a target='tab' href='https://docs.microsoft.com/en-us/previous-versions/tn-archive/ee692589(v=technet.10)'>https://docs.microsoft.com/en-us/previous-versions/tn-archive/ee692589(v=technet.10)</a> </pre> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > On Error Resume Next strComputer = "." blnDNSEnabledForWINSResolution = True blnWINSEnableLMHostsLookup = True strWINSHostLookupFile = "" strWINSScopeID = "WORKGROUP" Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set objNicConf = objWMIService.Get("Win32_NetworkAdapterConfiguration") WScript.Echo VbCrLf & "Host Name: " & strComputer & VbCrLf & _ " Attempting to enable WINS" intEnableWINS = objNicConf.EnableWINS(blnDNSEnabledForWINSResolution, _ blnWINSEnableLMHostsLookup, strWINSHostLookupFile, strWINSScopeID) If intEnableWINS = 0 Then WScript.Echo " Successfully enabled WINS on all network adapters." ElseIf intEnableWINS = 1 Then WScript.Echo " Successfully enabled WINS on all network adapters." & _ VbCrLf & " Must reboot." Else WScript.Echo " Unable to enable WINS." End If WScript.Echo VbCrLf & String(80, "-") Set colNicConfigs = objWMIService.ExecQuery _ ("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True") For Each objNicConfig In colNicConfigs WScript.Echo VbCrLf & _ " Network Adapter " & objNicConfig.Index & VbCrLf & _ " " & objNicConfig.Description WScript.Echo " DNS Enabled For WINS Resolution: " & _ objNicConfig.DNSEnabledForWINSResolution WScript.Echo " WINS Enable LMHosts Lookup: " & _ objNicConfig.WINSEnableLMHostsLookup WScript.Echo " WINS Host Lookup File: " & _ objNicConfig.WINSHostLookupFile WScript.Echo " WINS Scope ID: " & objNicConfig.WINSScopeID Next </pre> <br /> 어쨌든, 이렇게 변경하고 다시 이 글의 예제 코드를 실행하면 GetHostByAddress 메서드가 금방 값을 반환하게 됩니다. (당연히 네트워크 패킷에서도 한 개의 DNS Query만 나옵니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 혹시 다른 방법도 있을까요? 만약 특정 IP 주소만 GetHostByAddress로 넘기는 경우라면 NetBIOS 서비스를 비활성화하기 보다는 %WINDIR%\system32\drivers\etc 폴더에 있는 HOSTS 파일에 다음과 같이 항목을 하나 추가해 주면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 192.168.102.5 ANYNAME </pre> <br /> 사실 이런 경우에는 GetHostByAddress가 ANYNAME을 반환하므로 컴퓨터가 존재한다고 판단할 수 있는 여지가 좀 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 반면 정말로 DNS 측의 응답이 느린 것일 수도 있지 않을까요? (<a target='tab' href='https://www.sysnet.pe.kr/2/0/1069'>전에도 한번 소개했지만</a>) 다음의 글에 나오는 소스 코드를 이용하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > How to use the DnsQuery function to resolve host names and host addresses with Visual C++ .NET ; <a target='tab' href='https://support.microsoft.com/en-us/help/831226/how-to-use-the-dnsquery-function-to-resolve-host-names-and-host-addres'>https://support.microsoft.com/en-us/help/831226/how-to-use-the-dnsquery-function-to-resolve-host-names-and-host-addres</a> </pre> <br /> 간단하게 "q831226.exe -n [이름풀이주소] -t PTR -s [DNS서버주소]"와 같이 실행해 볼 수 있습니다. 가령 이 글의 예제 같은 경우라면 다음과 같이 실행합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > q831226.exe -n 192.168.102.5 -t PTR -s 164.124.101.2 </pre> <br /> 위의 코드는 NetBIOS 쿼리 없이 곧바로 DNS 조회만 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이참에, Dns.GetHostByAddress 메서드의 내부를 좀 볼까요? ^^ .NET Reflector로 보면 우선 InternalGetHostByAddress 메서드를 호출하는 것이 나오고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [Obsolete("GetHostByAddress is obsoleted for this type, please use GetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")] public static IPHostEntry GetHostByAddress(IPAddress address) { // ...[생략]... IPHostEntry hostByAddress = <span style='color: blue; font-weight: bold'>InternalGetHostByAddress</span>(address, false); // ...[생략]... return hostByAddress; } </pre> <br /> InternalGetHostByAddress 메서드는 상황에 따라 TryGetNameInfo 또는 OSSOCK.gethostbyaddr Win32 API를 호출합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > internal static IPHostEntry InternalGetHostByAddress(IPAddress address, bool includeIPv6) { // ...[생략]... if (Socket.LegacySupportsIPv6 | includeIPv6) { string name = <span style='color: blue; font-weight: bold'>TryGetNameInfo</span>(address, out success); // ...[생략]... } else { // ...[생략]... IntPtr nativePointer = UnsafeNclNativeMethods.OSSOCK.<span style='color: blue; font-weight: bold'>gethostbyaddr</span>(ref addr, Marshal.SizeOf(typeof(int)), ProtocolFamily.InterNetwork); // ...[생략]... } // ...[생략]... } </pre> <br /> 마지막으로 TryGetNameInfo의 경우 OSSOCK.GetNameInfoW Win32 API를 호출합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > internal static string TryGetNameInfo(IPAddress addr, out SocketError errorCode) { // ...[생략]... errorCode = UnsafeNclNativeMethods.OSSOCK.<span style='color: blue; font-weight: bold'>GetNameInfoW</span>(address.m_Buffer, address.m_Size, host, host.Capacity, null, 0, flags); // ...[생략]... } </pre> <br /> 결국 gethostbyaddr, GetNameInfoW API 모두 NbtNs 이름 조회를 포함하고 있으므로 존재하지 않는 IP에 대해 호출하는 경우 동일한 문제가 발생합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 재미있는 점이 하나 있다면, "Disable NetBIOS over TCP/IP" 설정을 하지 않은 상태에서도 NbtNs 추가 쿼리가 기본적으로 안 나오는 경우가 있다는 것입니다. 가령, 제가 테스트한 윈도우 10 머신 중에 2대는 저런 현상이 나왔던 반면 한 대는 NbtNs 추가 쿼리가 없었습니다. 또한, 테스트 용으로 구성한 Active Directory에 속한 가상 머신들도 모두 저런 지연 현상이 없습니다. 단지 물리 머신으로 AD에 속해 있던 2대의 머신은 또 저런 지연 현상이 있습니다. <br /> <br /> 혹시, NbtNs 추가 쿼리에 대한 정확한 환경 설정을 아시는 분은 덧글 부탁드립니다. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1429&boardid=331301885'>첨부 파일은 이 글의 예제 프로젝트를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3235
(왼쪽의 숫자를 입력해야 합니다.)