Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 6개 있습니다.)
.NET Framework: 221. Cache 영향을 받지 않는 DNS 이름 풀이
; https://www.sysnet.pe.kr/2/0/1069

.NET Framework: 264. 다중 LAN 카드 환경에서 Dns.GetHostAddresses(local)가 반환해 주는 IP의 우선순위는 어떻게 될까요?
; https://www.sysnet.pe.kr/2/0/1169

Windows: 154. PowerShell - Zone 별로 DNS 레코드 유형 정보 조회
; https://www.sysnet.pe.kr/2/0/11795

개발 환경 구성: 434. 존재하지 않는 IP 주소에 대한 Dns.GetHostByAddress/gethostbyaddr/GetNameInfoW 실행이 느리다면?
; https://www.sysnet.pe.kr/2/0/11852

개발 환경 구성: 435. 존재하지 않는 IP 주소에 대한 Dns.GetHostByAddress/gethostbyaddr/GetNameInfoW 실행이 느리다면? - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/11853

개발 환경 구성: 646. HOSTS 파일 변경 시 Edge 브라우저에 반영하는 방법
; https://www.sysnet.pe.kr/2/0/13089




존재하지 않는 IP 주소에 대한 Dns.GetHostByAddress/gethostbyaddr/GetNameInfoW 실행이 느리다면?

간단한 재현 코드는 다음과 같습니다.

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,
*/

(처음 실행 시의 GC 타임을 감안해도) 4.5초가 걸린다니, 만약 웹 애플리케이션에서 이것을 사용하면 장애 발생과 다를 바 없는 상황입니다. 도대체 왜 이런 결과가 나오는 걸까요?




원인을 밝히기 위해 드디어 ^^ netsh trace를 써먹을 때가 되었습니다.

Netsh의 네트워크 모니터링 기능
; https://www.sysnet.pe.kr/2/0/11801

위의 예제 코드를 다음의 batch 스크립트에 끼워서 실행시키고,

REM trace_net.bat
REM 관리자 권한으로 실행

netsh trace start capture=YES report=YES persistent=YES

ConsoleApp1.exe

netsh trace stop

%LOCALAPPDATA%\Temp\NetTraces 폴더에 생성된 etl 파일을 Network Monitor 프로그램으로 열어 보면, 패킷 캡처된 것들 중에 DNS Resolve와 관련된 세션을 2개 확인할 수 있습니다.

- All Traffic
  - Other Traffic
   - NetEvent ActivityID 1 - NDIS-PacketCapture
      SystemTrace 
    - NDISPacCap_MicrosoftWindowsNDISPacketCapture 
    ...[생략]...
     + IPv4 (192.168.100.25 - 164.124.101.2) ConvID = 18
     + IPv4 (192.168.100.25 - 192.168.102.5) ConvID = 21
    ...[생략]...

"ConvID = 18" 세션의 상세 보기를 하면,

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 

제 컴퓨터의 DNS 서버(164.124.101.2)로 IP 주소에 대한 이름 풀이를 위해 쿼리를 전송하고 응답으로 "Response - Name Error"를 받고 있습니다. 당연히 없는 컴퓨터의 IP 주소이므로 PTR 레코드에 따른 reverse DNS Lookup에 실패하는 것입니다. 그렇다면 그다음 나오는 "Conv = 21"은 뭘까요? 역시 상세 정보 보기로 하면,

202  ... 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
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

NbtNs 프로토콜로,

NbtNs
; http://www.pciqsatalk.com/2016/03/disable-lmnr-netbios.html

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.

다시 한번 존재하지 않는 IP로 이름 풀이를 시도하고 있습니다. 그렇습니다. 바로 저 통신이 문제의 지연 현상을 일으키고 있는 것입니다.




그렇다면 이제 문제 해결을 할 수 있게 되었습니다. NetBIOS 서비스를 사용하지 않도록 설정하면 되므로 각각의 네트워크 어댑터 설정에 가서 다음과 같이 "Disable NetBIOS over TCP/IP" 설정을 선택하면 됩니다.

dns_gethostbyaddress_2.jpg

그런데, 여기서 한 가지 문제가 있습니다. 만약 자신의 컴퓨터에 네트워크 어댑터가 논리적이든, 물리적이든 여러 개가 있다면 "Internet Protocol Version 4 (TCP/IPv4)"에 체크된 모든 어댑터에 대해서 저 설정을 해야 합니다. 가령 제 컴퓨터에서는 다음과 같이 좌측에 있는 5개 중에 3개의 어댑터가 "Internet Protocol Version 4 (TCP/IPv4)" 설정을 가지고 있어서 그 3개 모두 "Disable NetBIOS over TCP/IP" 설정을 해야만 했습니다.

dns_gethostbyaddress_3.png

아마도 현업에서라면 이 작업을 쉽게 할 수 있도록 프로그램을 만들거나, 다음의 글에 나오는 vbs 스크립트를 수정해 사용하면 될 것입니다.

Part 6: Scripting WINS on Clients
; https://docs.microsoft.com/en-us/previous-versions/tn-archive/ee692589(v=technet.10)

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

어쨌든, 이렇게 변경하고 다시 이 글의 예제 코드를 실행하면 GetHostByAddress 메서드가 금방 값을 반환하게 됩니다. (당연히 네트워크 패킷에서도 한 개의 DNS Query만 나옵니다.)




혹시 다른 방법도 있을까요? 만약 특정 IP 주소만 GetHostByAddress로 넘기는 경우라면 NetBIOS 서비스를 비활성화하기 보다는 %WINDIR%\system32\drivers\etc 폴더에 있는 HOSTS 파일에 다음과 같이 항목을 하나 추가해 주면 됩니다.

192.168.102.5 ANYNAME

사실 이런 경우에는 GetHostByAddress가 ANYNAME을 반환하므로 컴퓨터가 존재한다고 판단할 수 있는 여지가 좀 있습니다.




반면 정말로 DNS 측의 응답이 느린 것일 수도 있지 않을까요? (전에도 한번 소개했지만) 다음의 글에 나오는 소스 코드를 이용하면,

How to use the DnsQuery function to resolve host names and host addresses with Visual C++ .NET
; https://support.microsoft.com/en-us/help/831226/how-to-use-the-dnsquery-function-to-resolve-host-names-and-host-addres

간단하게 "q831226.exe -n [이름풀이주소] -t PTR -s [DNS서버주소]"와 같이 실행해 볼 수 있습니다. 가령 이 글의 예제 같은 경우라면 다음과 같이 실행합니다.

q831226.exe -n 192.168.102.5 -t PTR -s 164.124.101.2

위의 코드는 NetBIOS 쿼리 없이 곧바로 DNS 조회만 합니다.




이참에, Dns.GetHostByAddress 메서드의 내부를 좀 볼까요? ^^ .NET Reflector로 보면 우선 InternalGetHostByAddress 메서드를 호출하는 것이 나오고,

[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 = InternalGetHostByAddress(address, false);
    // ...[생략]...
    return hostByAddress;
}

InternalGetHostByAddress 메서드는 상황에 따라 TryGetNameInfo 또는 OSSOCK.gethostbyaddr Win32 API를 호출합니다.

internal static IPHostEntry InternalGetHostByAddress(IPAddress address, bool includeIPv6)
{
    // ...[생략]...
    if (Socket.LegacySupportsIPv6 | includeIPv6)
    {
        string name = TryGetNameInfo(address, out success);
        // ...[생략]...
    }
    else
    {
        // ...[생략]...
        IntPtr nativePointer = UnsafeNclNativeMethods.OSSOCK.gethostbyaddr(ref addr, Marshal.SizeOf(typeof(int)), ProtocolFamily.InterNetwork);
        // ...[생략]...
    }
    // ...[생략]...
}

마지막으로 TryGetNameInfo의 경우 OSSOCK.GetNameInfoW Win32 API를 호출합니다.

internal static string TryGetNameInfo(IPAddress addr, out SocketError errorCode)
{
    // ...[생략]...
    errorCode = UnsafeNclNativeMethods.OSSOCK.GetNameInfoW(address.m_Buffer, address.m_Size, host, host.Capacity, null, 0, flags);
    // ...[생략]...
}

결국 gethostbyaddr, GetNameInfoW API 모두 NbtNs 이름 조회를 포함하고 있으므로 존재하지 않는 IP에 대해 호출하는 경우 동일한 문제가 발생합니다.




재미있는 점이 하나 있다면, "Disable NetBIOS over TCP/IP" 설정을 하지 않은 상태에서도 NbtNs 추가 쿼리가 기본적으로 안 나오는 경우가 있다는 것입니다. 가령, 제가 테스트한 윈도우 10 머신 중에 2대는 저런 현상이 나왔던 반면 한 대는 NbtNs 추가 쿼리가 없었습니다. 또한, 테스트 용으로 구성한 Active Directory에 속한 가상 머신들도 모두 저런 지연 현상이 없습니다. 단지 물리 머신으로 AD에 속해 있던 2대의 머신은 또 저런 지연 현상이 있습니다.

혹시, NbtNs 추가 쿼리에 대한 정확한 환경 설정을 아시는 분은 덧글 부탁드립니다. ^^

(첨부 파일은 이 글의 예제 프로젝트를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 6/28/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 106  107  108  109  110  111  112  113  [114]  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11075정성태10/20/201621001Windows: 127. Convert-WindowsImage.ps1 사용 방법 정리
11074정성태10/20/201629304Windows: 126. Windows Server 2016 평가판을 정식 버전으로 라이선스 변경하는 방법
11073정성태10/20/201625326.NET Framework: 613. 윈도우 데스크톱 응용 프로그램(예: Console)에서 알림 메시지(Toast notifications) 띄우기 [1]파일 다운로드1
11072정성태10/20/201621867VC++: 102. 새로 추가한 ATL COM 객체가 regsvr32.exe로 등록이 안 되는 문제
11071정성태10/20/201625408.NET Framework: 612. UWP(유니버설 윈도우 플랫폼) 앱에서 콜백 함수 내에서의 UI 요소 접근 방법 [1]
11070정성태10/20/201619621Windows: 125. 윈도우 서버 2016 마이그레이션
11069정성태10/19/201627333.NET Framework: 611. C++ 개발자들을 위한 C# Thread 동작 방식 [2]
11068정성태10/19/201630525Windows: 124. 윈도우 운영체제의 시간 함수 (5) - TSC(Time Stamp Counter)와 QueryPerformanceCounter [12]파일 다운로드1
11067정성태10/18/201626844Windows: 123. 윈도우 운영체제의 시간 함수 (4) - RTC, TSC, PM Clock, HPET Timer [2]
11066정성태10/17/201624496Windows: 122. 윈도우 운영체제의 시간 함수 (3) - QueryInterruptTimePrecise, QueryInterruptTime 함수파일 다운로드1
11065정성태10/15/201629926Windows: 121. 윈도우 운영체제의 시간 함수 (2) - Sleep 함수의 동작 방식 [1]
11064정성태10/14/201621631.NET Framework: 610. C# - WaitOnAddress Win32 API 사용파일 다운로드1
11063정성태10/14/201637734Windows: 120. 윈도우 운영체제의 시간 함수 (1) - GetTickCount와 timeGetTime의 차이점 [5]파일 다운로드1
11062정성태10/12/201618342오류 유형: 361. WCF .svc 호출 시 Could not find a base address that matches scheme net.tcp 예외
11061정성태10/12/201630467오류 유형: 360. IIS - 500.19 오류 (0x80070021)
11060정성태10/12/201622940오류 유형: 359. WCF - .svc 요청시 404 Not Found
11059정성태10/11/201627186.NET Framework: 609. WPF - 다중 스레드 환경에서 데이터 바인딩의 INotifyPropertyChanged.PropertyChanged에 대한 배려 [1]파일 다운로드1
11058정성태10/8/201622514개발 환경 구성: 303. Windows 10 Bash Shell - 한글 환경을 영문으로 바꾸고 싶다면?
11057정성태10/8/201617912오류 유형: 358. Windows 10 bash shell - sudo: unable to resolve host ...
11056정성태10/8/201620787개발 환경 구성: 302. Windows 10 bash shell 시작 시 [...] packages can be updated.
11055정성태10/8/201621834.NET Framework: 608. double 값을 구할 때는 반드시 피연산자를 double로 형변환! [6]
11054정성태10/5/201625829개발 환경 구성: 301. "Let's Encrypt" SSL 인증서를 Azure Cloud Services(classic)에 업데이트하는 방법
11053정성태10/5/201620551.NET Framework: 607. C# try/catch/finally의 IL 코드 표현
11052정성태9/27/201634699개발 환경 구성: 300. C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 [7]파일 다운로드1
11051정성태9/25/201621877개발 환경 구성: 299. docker - c:\programdata\docker\windowsfilter 폴더 정리하는 방법파일 다운로드1
11050정성태9/24/201626609VC++: 101. 반올림하지 않고 double 변수 값 출력하는 방법 [3]
... 106  107  108  109  110  111  112  113  [114]  115  116  117  118  119  120  ...