Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 3개 있습니다.)
Windows: 171. "Administered port exclusions" 설명
; https://www.sysnet.pe.kr/2/0/12293

Windows: 172. "Administered port exclusions"이 아닌 포트 범위 항목을 삭제하는 방법
; https://www.sysnet.pe.kr/2/0/12305

닷넷: 2158. C# - 소켓 포트를 미리 시스템에 등록/예약해 사용하는 방법(Port Exclusion Ranges)
; https://www.sysnet.pe.kr/2/0/13439




C# - 소켓 포트를 미리 시스템에 등록/예약해 사용하는 방법(Port Exclusion Ranges)

아래와 같은 질문이 있군요. ^^

SocketException 액세스 권한에 의해 숨겨진 소켓에 액세스를 시도했습니다 오류
; https://forum.dotnetdev.kr/t/socketexception/8898

excludedportrange에 대해서는 저도 예전에 글을 남긴 적이 있습니다.

"Administered port exclusions" 설명
; https://www.sysnet.pe.kr/2/0/12293

그리고, 이러한 포트 점유로 인해 개인적으로 겪은 오류도 제법 됩니다. ^^;

소켓 바인딩 시 "System.Net.Sockets.SocketException: An attempt was made to access a socket in a way forbidden by its access permissions" 오류 발생
; https://www.sysnet.pe.kr/2/0/12240

Visual Studio - 웹 애플리케이션 실행 시 "Unable to connect to web server 'IIS Express'." 오류 발생
; https://www.sysnet.pe.kr/2/0/12265

Bitvise - Address is already in use; bind() in ListeningSocket::StartListening() failed: Windows error 10013: An attempt was made to access a socket in a way forbidden by its access permissions.
; https://www.sysnet.pe.kr/2/0/12295

SQL Server 시작 오류 - error code 10013
; https://www.sysnet.pe.kr/2/0/12306

Nox 실행이 안 되는 경우 - Unable to bind to the underlying transport for ...
; https://www.sysnet.pe.kr/2/0/12351

ASP.NET 0x80131620 Failed to bind to address
; https://www.sysnet.pe.kr/2/0/12492

Tomcat 실행 시 Failed to initialize connector [Connector[HTTP/1.1-8080]] 오류
; https://www.sysnet.pe.kr/2/0/12671

IntelliJ에서 Java webapp 실행 시 "Address localhost:1099 is already in use" 오류
; https://www.sysnet.pe.kr/2/0/12672

질문자는 왜 excludedportrange 제한이 있는가에 대한 질문을 했지만, 사실 이미 이름에 그 이유가 있습니다. 소켓 프로그래밍을 해보신 분이라면, 아마도 아래와 같은 오류 상황을 적지 않게 접했을 텐데요,

서버용 Socket에서 사용하는 포트가 충돌한다면?
; https://www.sysnet.pe.kr/2/0/1807

그렇습니다, 우리가 만든 서비스가 또는 응용 프로그램이 특정 포트 범위를 안전하게 사용하고 싶은 경우 excludedportrange에 그 범위를 등록해 두면 되는 것입니다.




그런데, 얼핏 생각하면 이게 말이 안 됩니다. 이유를 볼까요? ^^ 실습을 위해, 12000 포트를 하나 점유한 다음,

// "관리자 권한"으로 실행

using System.Runtime.InteropServices;

namespace ConsoleApp1;

internal class Program
{
    [DllImport("Iphlpapi.dll")]
    internal static extern uint CreatePersistentTcpPortReservation(ushort startPort, ushort numberOfPorts, out long token);

    static void Main(string[] args)
    {
        ushort portReserved = (ushort)IPAddress.HostToNetworkOrder((short)12000);

        error = CreatePersistentTcpPortReservation(portReserved, 1, out pToken);
        if (error != 0)
        {
            Console.WriteLine($"Error: {error}");
            return;
        }

        Console.WriteLine("Success: " + pToken);
    }
}

이제 소켓을 12000 포트에 바인딩해 보면,

using System.Net;
using System.Net.Sockets;

namespace ConsoleApp1;

internal class Program
{
    static void Main(string[] args)
    {
        IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 12000);
        Socket listener = new Socket(SocketType.Stream, ProtocolType.Tcp);

        try
        {
            listener.Bind(localEndPoint);
            listener.Listen(10);

            while (true)
            {
                Console.WriteLine("Waiting for a connection...");
                listener.Accept();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }

    }
}

이렇게 오류가 발생합니다.

C:\temp> ConsoleApp1.exe
System.Net.Sockets.SocketException (10013): An attempt was made to access a socket in a way forbidden by its access permissions.
   at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName)
   at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
   at System.Net.Sockets.Socket.Bind(EndPoint localEP)
   at ConsoleApp1.Program.Main(String[] args) in C:\ConsoleApp1\ConsoleApp1\Program.cs:line 38

우리가 원했던 바이긴 한데, 정작 우리도 그 포트를 사용할 수 없게 된 것입니다. ^^




당연히 이런 경우를 위해, 소켓을 바인딩하는 별도의 방법이 있고, 아래의 문서에 C++ 예제 코드와 함께 상세하게 나와 있습니다.

CreatePersistentTcpPortReservation function (iphlpapi.h)
; https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-createpersistenttcpportreservation

그걸 C#으로 옮기면 Socket.IOControl 메서드를 이용해 다음과 같이 포팅할 수 있습니다.

// "관리자 권한"으로 실행

using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;

namespace ConsoleApp1;

internal class Program
{
    [DllImport("Iphlpapi.dll")] // iphlpapi.h
    internal static extern uint LookupPersistentTcpPortReservation(ushort startPort, ushort numberOfPorts, out long token);

    static void Main(string[] args)
    {
        int portNumber = 12000;

        ushort portReserved = (ushort)IPAddress.HostToNetworkOrder((short)portNumber);
        uint status = LookupPersistentTcpPortReservation(portReserved, 1, out long portToBind);

        if (status != 0)
        {
            Console.WriteLine($"Lookup failed, creating reservation (port: {portNumber})");
            return;
        }

        Console.WriteLine("PID: " + System.Diagnostics.Process.GetCurrentProcess().Id);
        
        Socket listener = new Socket(SocketType.Stream, ProtocolType.Tcp);

        const int SIO_ASSOCIATE_PORT_RESERVATION = -1744830362;
        byte[] resToken = BitConverter.GetBytes(portToBind);

        int result = listener.IOControl(SIO_ASSOCIATE_PORT_RESERVATION, resToken, null);
        if (result != 0)
        {
            Console.WriteLine($"WSAIoctl(SIO_ASSOCIATE_PORT_RESERVATION) failed with error = {result}");
            return;
        }

        IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, portNumber);

        try
        {
            listener.Bind(localEndPoint);
            listener.Listen(10);

            while (true)
            {
                Console.WriteLine("Waiting for a connection...");
                listener.Accept();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}

실행해 보면, 정상적으로 12000 포트로 바인딩하는 것을 볼 수 있고, netstat로도 확인이 됩니다.

c:\temp> netstat -ano | findstr 12000
  TCP    0.0.0.0:12000          0.0.0.0:0              LISTENING       55708




주의할 것은, SIO_ASSOCIATE_PORT_RESERVATION 옵션으로 IOControl을 수행하려면 관리자 권한이 있어야 한다는 점입니다. 그렇지 않으면 다음과 같은 예외가 발생합니다.

System.Net.Sockets.SocketException
  HResult=0x80004005
  Message=An attempt was made to access a socket in a way forbidden by its access permissions.
  Source=System.Net.Sockets
  StackTrace:
   at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName)
   at System.Net.Sockets.Socket.IOControl(Int32 ioControlCode, Byte[] optionInValue, Byte[] optionOutValue)
   at ConsoleApp1.Program.Main(String[] args) in C:\ConsoleApp1\ConsoleApp1\Program.cs:line 43

그러니까, CreatePersistentTcpPortReservation도 관리자 권한이 필요하고, 그렇게 해서 등록한 포트 영역을 사용하기 위해서도 관리자 권한이 필요한 것인데요, 이런 면에서 봤을 때 "NT 서비스"로 실행하는 응용 프로그램 등의 구성 요소에서 사용하는 용도로 쓸 수 있겠습니다.

그리고 문서에 나오듯이, 이런 점을 이용해 다음의 2가지 시나리오로 사용하면 됩니다.

Applications and services which need to reserve ports fall into two categories.

The first category includes components which need a particular port as part of their operation. Such components will generally prefer to specify their required port at installation time (in an application manifest, for example).

The second category includes components which need any available port or block of ports at runtime.





한 가지 재미있는 건, 정작 netsh로 "Administered port exclusions" 영역을 등록한 경우에는,

[추가]
netsh int ipv4 Add excludedportrange protocol=tcp startport=12000 numberofports=1 store=persistent
netsh int ipv6 Add excludedportrange protocol=tcp startport=12000 numberofports=1 store=persistent

[확인]
netsh int ipv4 show excludedportrange protocol=tcp
netsh int ipv6 show excludedportrange protocol=tcp

[삭제]
netsh int ipv4 delete excludedportrange protocol=tcp startport=12000 numberofports=1
netsh int ipv6 delete excludedportrange protocol=tcp startport=12000 numberofports=1

소켓 바인딩이 허용된다는 점입니다.

// Administered port exclusions 영역의 포트는 직접 바인딩 가능

IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 12000);
listener.Bind(localEndPoint);

그래서, 왜 "Administered" 영역이 있는가에 대한 이유가 불분명합니다. 이건 어쩌면 효력은 없지만, 이 설정을 알고 있는 프로그램들끼리의 약속을 잡아 놓는 것이 아닌가... 하는 추측만 합니다. 예를 들어, 대표적으로 Hyper-V에서 "Administered" 포트 영역을 설정하는데, 그 영역을 설정해 두었으니, (적어도 마이크로소프트가 만든) 다른 소프트웨어에서는 그것을 인지하고 다른 영역의 포트를 사용하도록 유도한다는 식의... ^^ 개인적인 가정입니다.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)





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

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/11/2023]

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

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  67  68  69  70  71  72  [73]  74  75  ...
NoWriterDateCnt.TitleFile(s)
12106정성태1/8/202019580VC++: 136. C++ - OSR Driver Loader와 같은 Legacy 커널 드라이버 설치 프로그램 제작 [1]
12105정성태1/8/202018079디버깅 기술: 153. C# - PEB를 조작해 로드된 DLL을 숨기는 방법
12104정성태1/7/202019260DDK: 9. 커널 메모리를 읽고 쓰는 NT Legacy driver와 C# 클라이언트 프로그램 [4]
12103정성태1/7/202022357DDK: 8. Visual Studio 2019 + WDK Legacy Driver 제작- Hello World 예제 [1]파일 다운로드2
12102정성태1/6/202018740디버깅 기술: 152. User 권한(Ring 3)의 프로그램에서 _ETHREAD 주소(및 커널 메모리를 읽을 수 있다면 _EPROCESS 주소) 구하는 방법
12101정성태1/5/202018943.NET Framework: 876. C# - PEB(Process Environment Block)를 통해 로드된 모듈 목록 열람
12100정성태1/3/202016420.NET Framework: 875. .NET 3.5 이하에서 IntPtr.Add 사용
12099정성태1/3/202019246디버깅 기술: 151. Windows 10 - Process Explorer로 확인한 Handle 정보를 windbg에서 조회 [1]
12098정성태1/2/202018999.NET Framework: 874. C# - 커널 구조체의 Offset 값을 하드 코딩하지 않고 사용하는 방법 [3]
12097정성태1/2/202017098디버깅 기술: 150. windbg - Wow64, x86, x64에서의 커널 구조체(예: TEB) 구조체 확인
12096정성태12/30/201919830디버깅 기술: 149. C# - DbgEng.dll을 이용한 간단한 디버거 제작 [1]
12095정성태12/27/201921517VC++: 135. C++ - string_view의 동작 방식
12094정성태12/26/201919233.NET Framework: 873. C# - 코드를 통해 PDB 심벌 파일 다운로드 방법
12093정성태12/26/201918798.NET Framework: 872. C# - 로딩된 Native DLL의 export 함수 목록 출력파일 다운로드1
12092정성태12/25/201917646디버깅 기술: 148. cdb.exe를 이용해 (ntdll.dll 등에 정의된) 커널 구조체 출력하는 방법
12091정성태12/25/201919919디버깅 기술: 147. pdb 파일을 다운로드하기 위한 symchk.exe 실행에 필요한 최소 파일 [1]
12090정성태12/24/201920016.NET Framework: 871. .NET AnyCPU로 빌드된 PE 헤더의 로딩 전/후 차이점 [1]파일 다운로드1
12089정성태12/23/201918907디버깅 기술: 146. gflags와 _CrtIsMemoryBlock을 이용한 Heap 메모리 손상 여부 체크
12088정성태12/23/201917892Linux: 28. Linux - 윈도우의 "Run as different user" 기능을 shell에서 실행하는 방법
12087정성태12/21/201918351디버깅 기술: 145. windbg/sos - Dictionary의 entries 배열 내용을 모두 덤프하는 방법 (do_hashtable.py) [1]
12086정성태12/20/201920797디버깅 기술: 144. windbg - Marshal.FreeHGlobal에서 발생한 덤프 분석 사례
12085정성태12/20/201918785오류 유형: 586. iisreset - The data is invalid. (2147942413, 8007000d) 오류 발생 - 두 번째 이야기 [1]
12084정성태12/19/201919240디버깅 기술: 143. windbg/sos - Hashtable의 buckets 배열 내용을 모두 덤프하는 방법 (do_hashtable.py) [1]
12083정성태12/17/201922221Linux: 27. linux - lldb를 이용한 .NET Core 응용 프로그램의 메모리 덤프 분석 방법 [2]
12082정성태12/17/201920514오류 유형: 585. lsof: WARNING: can't stat() fuse.gvfsd-fuse file system
12081정성태12/16/201922360개발 환경 구성: 465. 로컬 PC에서 개발 중인 ASP.NET Core 웹 응용 프로그램을 다른 PC에서도 접근하는 방법 [5]
... 61  62  63  64  65  66  67  68  69  70  71  72  [73]  74  75  ...