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

TCP 동시 접속 (연결이 아닌) 시도를 1개로 제한한 서버

고객사로부터 희한한 버그 이슈가 나왔습니다.

제니퍼 에이전트를 설치한 웹 사이트가 제니퍼 서버가 꺼져 있으면 자사 웹 사이트의 로그인이 느려진다.

라는 것입니다. ^^; 제니퍼 닷넷 에이전트가 고객사의 "로그인 관련 코드"를 지연할 만한 어떤 요인도 없으므로, 그럴 수가 없는데... 그렇다는 것입니다.

실제로 원격으로 접속하니... ^^; 정말 그런 현상이 나타났습니다. 아니... ^^; 어떻게 이럴 수가 있는지 황당하기만 했는데요, 또한 로그인이 지연되는 그 순간의 메모리 덤프를 받아 callstack을 조사해봤지만 (당연히) 로그인과 관련된 호출에서 제니퍼 닷넷 에이전트 측의 메서드 호출은 하나도 없었습니다.

어쨌든 이런 현상은 대부분 고객사 측이 구성한 "보안 설정"에 의한 것이 많으므로 그쪽을 살펴보라고 했습니다. 하지만 그에 대해 고객사는 아무런 문제가 없다는 회신이 있었습니다.

어쩔 수 없군요, 이런 경우는 3rd-party 업체가 어떤 식으로든 원인 규명을 해야 합니다. ^^




제니퍼 에이전트의 기능을 (말도 안 되지만, 혹시나 싶어) 이거저거 관련 없는 옵션들을 제거하며 테스트를 해봤지만 해당 현상은 사라지지 않았습니다.

그러다 최후로 남은 기능은, 단순히 제니퍼 서버로 TCP 소켓을 연결하는 코드에 이르렀습니다. 여기서 문제의 원인이 모습을 드러냈는데요, TCP 소켓을 제니퍼 서버로 연결하는 그 코드를 비활성화시키면 고객사의 로그인 지연 문제가 발생하지 않았던 것입니다.

재미있지 않나요? ^^

그래서 간단한 소스 코드를 다음과 같이 작성해 봤습니다.

using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;

// 희한한 건, 고객사 서버에서는 보안 도구가 막고 있는 것인지 다른 버전(3.5, 4.0)의 csc.exe는 실행 시 알 수 없는 오류가 발생했습니다.
// 그래서 어쩔 수 없이 유일하게 실행 가능했던 .NET Framework 2.0의 C# 컴파일러를 사용해야 했고,
// 내부 구문도 C# 2.0 버전에 맞춰야 했습니다.
// C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc test.cs

namespace ConsoleApp1
{
    internal class Program
    {
        static string _host;
        static int _port;

        static void Main(string[] args)
        {
            _host = args[0];
            _port = int.Parse(args[1]);

            ParameterizedThreadStart ts = new ParameterizedThreadStart(ConnectProxy);
            Thread t = new Thread(ts);

            t.Start();

            Thread.Sleep(500);
            Connect(_host, 80);

            t.Join();
        }

        static void ConnectProxy(object arg)
        {
            Connect(_host, _port);
        }

        static void Connect(string host, int port)
        {
            using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
            {
                Console.WriteLine(DateTime.Now + " Trying to connect " + host + ":" + port);
                try
                {
                    socket.Connect(host, port);
                    Console.WriteLine(DateTime.Now + " Connected!");
                }
                catch (Exception e)
                {
                    Console.WriteLine(DateTime.Now + " " + e.Message);
                }
            }
        }
    }
}

위의 코드는 스레드 2개에서 각각 "지정된 포트"와 80 포트로 연결을 시도합니다. 그래서 만약 "지정된 서버"는 있지만 "지정된 포트"에서 대기하고 있는 TCP 서버가 없고 80 포트 대기는 있다면 다음과 같은 식의 출력이 나타납니다.

c:\temp> test www.microsoft.com 15000
2022-11-04 오후 9:28:23 Trying to connect www.microsoft.com:15000
2022-11-04 오후 9:28:23 Trying to connect www.microsoft.com:80
2022-11-04 오후 9:28:23 Connected!
2022-11-04 오후 9:28:44 A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond 104.76.29.199:15000

보는 바와 같이 80 포트는 23초에 접속 시작해 곧바로 "Connected"가 떴고, 반면 15000 포트로의 접속은 21초의 지연 시간 후 접속할 수 없다는 오류가 발생합니다.

하지만 고객사 서버에서는 다음과 같은 식의 출력이 나왔습니다.

c:\temp> test www.microsoft.com 15000
2022-11-04 오후 9:28:23 Trying to connect www.microsoft.com:15000
2022-11-04 오후 9:28:23 Trying to connect www.microsoft.com:80
2022-11-04 오후 9:28:44 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다
2022-11-04 오후 9:28:44 Connected

15000 포트의 접속 시도로 서버에서는 SYN_SENT 상태의 소켓이 하나 남게 됩니다. 그 상태에서는 다른 TCP 연결 시도가 막혀서 이전의 TCP 소켓 자원이 해제될 때까지 연결을 맺지 못하고 있습니다. 그러다 21초 후에 SYN_SENT 상태의 소켓이 사라지면서 곧바로 80 포트로 접속을 시도하던 연결이 맺어지게 됩니다.

정리하면, 해당 고객사의 "어떤 알 수 없는 보안 도구"가 "EXE 프로세스 당 1개"의 TCP 동시 접속 시도만 허용하도록 막고 있는 것입니다.




이 문제를 고객사 상황과 연결하면 현상이 해석됩니다.

제니퍼 에이전트는 제니퍼 서버와 TCP 통신을 하는데, 이때 만약 서버가 끊기면 제니퍼 에이전트는 주기적으로 TCP 연결을 서버로 시도하게 됩니다. 물론, 서버가 없기 때문에 21초 동안 SYN_SENT 상태의 소켓이 남게 되고 그때 해당 웹 애플리케이션에서 접속하려는 로그인 서버로의 TCP 연결이 방해를 받게 된 것입니다.

그런데, 왜? 로그인 서버로의 연결만 방해를 받는 걸까요?

사실 해당 웹 사이트는 DB 호출도 사용하고, 그 외 다른 서버로의 HTTP API 호출도 사용하고 있었습니다. 하지만 이런 호출들이 방해를 받지 않은 것은 DB의 경우 Connection Pool을 유지하기 때문에 신규 연결이 아니라면 TCP 연결이 생성되지 않아 괜찮았던 것입니다. 또한 HTTP API 호출도 닷넷 내부적으로는 소켓 풀을 운영하기 때문에 역시 신규 연결이 필요한 상황이 아니면 TCP 연결을 하지 않아 표면상 서비스 문제를 겪지 않았습니다.

하지만 로그인 서버로의 연결은 달랐습니다. 해당 고객사의 로그인은 DB나 다른 HTTP 호출을 이용해 확인하는 방식이 아니었습니다. 로그인 지연 현상이 발생할 때 뜬 덤프의 콜 스택을 보면 이렇게 Active Directory에 대한 호출이 있었음을 확인할 수 있었는데요,

DomainNeutralILStubClass.IL_STUB_PInvoke(System.String, System.String, System.String, Int32, System.Guid ByRef, System.Object ByRef)
System.DirectoryServices.Interop.UnsafeNativeMethods.ADsOpenObject(System.String, System.String, System.String, Int32, System.Guid ByRef, System.Object ByRef)
System.DirectoryServices.DirectoryEntry.Bind(Boolean)
System.DirectoryServices.DirectoryEntry.Bind()
System.DirectoryServices.DirectoryEntry.get_AdsObject()
System.DirectoryServices.DirectorySearcher.FindAll(Boolean)
System.DirectoryServices.DirectorySearcher.FindOne()
...[생략]...
System.Web.UI.WebControls.Button.RaisePostBackEvent(System.String)
System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
System.Web.UI.Page.ProcessRequest()
System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)
System.Web.HttpApplication+ApplicationStepManager.ResumeSteps(System.Exception)
System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext, System.AsyncCallback, System.Object)
System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest)
System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)
DomainNeutralILStubClass.IL_STUB_COMtoCLR(Int64, Int32, IntPtr)

결국, ADsOpenObject 메서드는 호출 때마다 매번 TCP 연결을 새로 맺는 동작을 했기 때문에 저렇게 특이한 현상을 나타나는데 기여를 했던 것입니다.




위의 상황에서 가장 간단한 해결 방법은 보안 도구가 막고 있는 "TCP 동시 접속 시도" 수를 늘리는 것입니다. 하지만, 경험상 대개의 경우 고객사는 일단 설치된 서버의 설정을 바꾸는 것을 원치 않습니다.

그래서 제니퍼 에이전트 측에 제니퍼 서버로 TCP 연결을 먼저 맺지 않고 UDP로 echo 테스트를 먼저 한 다음 맺는 방식을 추가함으로써 일단 이슈를 종료했습니다.

하지만, 이건 아마도 언젠가 해당 고객사의 발목을 잡을 만한 엉뚱한 보안 대책이라고 볼 수 있습니다. 왜냐하면, 고객사의 경우 HTTP 호출을 다수 하고 있기 때문에 만약 그 서버들 중 하나라도 접속이 끊기게 된다면 마찬가지 상황에 빠질 것이기 때문입니다. 게다가 이번 경우에는 제니퍼 에이전트가 TCP 연결 하나만을 일정 주기로 재시도하고 있기 때문에 크게 문제가 되진 않았지만, 만약 다수의 HTTP API 호출이 끊겼다가 새로 TCP 연결을 시도하는 SYN_SENT가 누적된다면 아예 그 사이트는 로그인 조차 거의 할 수 없는 상태로 진행될 것입니다.

또한, 그런 비정상적인 상황을 해결하기 위해 종종 하는 iisreset은 기존에 잘 연결되고 있던 DB나 살아 있는 HTTP API 서버로의 연결을 새로 맺게 만드는 과정을 요구하면서, SYN_SENT를 누적시키는 단 하나의 HTTP API 서버로 인해 더욱 심각한 장애 상황을 일으킬 것입니다.




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







[최초 등록일: ]
[최종 수정일: 3/15/2024]

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

비밀번호

댓글 작성자
 




... 181  182  183  184  [185]  186  187  188  189  190  191  192  193  194  195  ...
NoWriterDateCnt.TitleFile(s)
380정성태10/28/200628048    답변글 .NET Framework: 74.3. 웹 서비스와 닷넷 리모팅으로써의 WCF 구현파일 다운로드1
381정성태10/28/200630406    답변글 .NET Framework: 74.4. WCF 서비스 참조 추가 메뉴 [2]
382정성태10/28/200636438    답변글 .NET Framework: 74.5. WCF 서비스를 IIS에서 호스팅하는 방법파일 다운로드1
383정성태10/28/200631242    답변글 .NET Framework: 74.6. IIS 6.0: 다중 Endpoint 제공파일 다운로드1
384정성태10/28/200628194    답변글 .NET Framework: 74.7. IIS 7.0: 다중 Endpoint 제공
389정성태11/11/200631011    답변글 .NET Framework: 74.8. WCF에 SSL 적용 (1) - Httpcfg.exe 도구를 이용한 SSL 설정
390정성태11/6/200628175    답변글 .NET Framework: 74.9. WCF에 SSL 적용 (2) - 서비스 제작파일 다운로드1
356정성태10/7/200623752COM 개체 관련: 19. COM의 Apartment를 이해해 보자. [8]
386light10/30/200618664    답변글 COM 개체 관련: 19.1. [답변]: COM 객체를 글로벌마샬으로 만든후, 사용한다.
355정성태10/9/200626380개발 환경 구성: 19. Internet_Zone 하위에 새로운 코드 그룹을 추가하는 예제 [4]파일 다운로드2
353정성태12/31/200634719개발 환경 구성: 18. 윈도우즈 인증서 서비스 이야기 [3]
354정성태10/23/200637080    답변글 개발 환경 구성: 18.1. 윈도우즈 인증서 서비스 설치
372정성태12/31/200639064    답변글 개발 환경 구성: 18.2. 웹 사이트에 SSL을 적용 [3]
373정성태10/24/200630311    답변글 개발 환경 구성: 18.3. 사용자 입장에서의 HTTPS 접근 (1)
374정성태10/25/200627737    답변글 개발 환경 구성: 18.4. 사용자 입장에서의 HTTPS 접근 (2)
391정성태11/7/200631995    답변글 개발 환경 구성: 18.5. 사용자 인증서 발급
392정성태11/11/200645007    답변글 개발 환경 구성: 18.6. 인증서 관리 (1) - 내보내기/가져오기
394정성태11/9/200629622    답변글 개발 환경 구성: 18.7. 인증서 관리 (2) - 개인키를 내보낼 수 있는 유형의 인증서 발급 [1]
395정성태11/9/200641611    답변글 개발 환경 구성: 18.8. 인증서 관리 (3) - 인증서 MMC 관리자 사용
414정성태12/23/200633401    답변글 개발 환경 구성: 18.9. CRL(Certificate Revocation List) 관리
428정성태12/31/200646272    답변글 개발 환경 구성: 18.10. IIS 7 - SSL 사이트 설정하는 방법 [4]
429정성태12/31/200632498    답변글 개발 환경 구성: 18.11. 서비스를 위한 인증서 설치
352정성태10/2/200622116개발 환경 구성: 17. VPC에 Linux 설치하는 방법 [1]
351정성태10/8/200624555개발 환경 구성: 16. 성태의 무식한(!) 리눅스 탐방기. [4]
349정성태9/26/200623156디버깅 기술: 10. C++/CLI에서 제공되는 명시적인 파괴자의 비밀
347정성태10/6/200627081디버깅 기술: 9. .NET IDisposable 처리 정리 [1]
... 181  182  183  184  [185]  186  187  188  189  190  191  192  193  194  195  ...