본문 바로가기
서버연구 블로그 서버연구 블로그

SYN Flood와 TCP SYN Queue의 관계 분석

읽는 시간 약 19분

웹사이트 접속 장애가 발생하면 가장 먼저 CPU나 메모리부터 확인하는 경우가 많습니다.

그런데 CPU 사용률은 20% 수준이고 메모리도 충분하며 Nginx 프로세스까지 정상인데, 새로운 사용자는 계속 접속에 실패하는 상황이 있습니다.

이럴 때 애플리케이션 로그만 들여다봐서는 원인을 찾지 못할 수 있습니다.

문제가 웹서버까지 도달하기 전, TCP 연결을 만드는 단계에서 발생하고 있기 때문입니다.

대표적인 사례가 SYN Flood입니다.

SYN Flood를 단순히 “SYN 패킷을 많이 보내는 공격”으로만 이해하면 실제 서버 장애를 분석할 때 도움이 되지 않습니다. 중요한 것은 대량의 SYN이 Linux TCP 연결 처리 구조에서 어떤 상태를 만들고, 그것이 정상 사용자의 연결에 어떤 영향을 주는지를 이해하는 것입니다.

이번 글에서는 바로 그 부분을 살펴보겠습니다.

SYN Queue는 TCP 연결이 완성되기 전의 대기 공간이다

앞에서 TCP Backlog를 다루면서 서버가 연결 요청을 받는다고 즉시 애플리케이션에 전달하는 것은 아니라는 점을 살펴봤습니다.

이번 글에서는 전체 Connection 구조를 다시 길게 설명하지 않고 SYN Queue에만 집중하겠습니다.

TCP 연결은 기본적으로 다음 과정을 거칩니다.

Client                         Server

SYN             ------------>

                <------------  SYN + ACK

ACK             ------------>

          TCP Connection Established

클라이언트가 SYN을 전송하면 서버는 SYN/ACK로 응답합니다.

하지만 이 시점에는 아직 연결이 완성되지 않았습니다.

서버 입장에서는 클라이언트의 마지막 ACK를 받아야 3-Way Handshake가 끝납니다. 그동안 서버에는 아직 완전히 연결되지 않은 요청이 존재하게 되고 Linux에서는 이런 연결을 SYN_RECV 상태로 볼 수 있습니다.

개념적으로 보면 흐름은 이렇습니다.

SYN 도착
   ↓
미완성 Connection 관리
   ↓
SYN_RECV
   ↓
마지막 ACK 수신
   ↓
Handshake 완료
   ↓
accept()를 기다리는 연결
   ↓
Application

Linux에서는 미완성 연결 요청 수와 관련해 net.ipv4.tcp_max_syn_backlog라는 커널 파라미터를 제공합니다. 반면 현대 Linux에서 애플리케이션이 listen()에 전달하는 backlog는 연결이 완료된 뒤 accept()를 기다리는 쪽의 대기열과 관련됩니다.

둘을 같은 Queue로 생각하면 SYN Flood 장애를 분석할 때 방향이 틀어지기 쉽습니다.

정상적인 SYN 요청은 오래 머물지 않는다

정상 사용자가 서버에 접속했다고 가정해보겠습니다.

사용자가 SYN을 보내고 서버가 SYN/ACK를 반환합니다. 네트워크에 특별한 문제가 없다면 클라이언트는 다시 ACK를 보내고 연결이 완료됩니다.

그래서 평상시에는 SYN_RECV 상태의 연결이 생기더라도 대부분 빠르게 다음 단계로 넘어갑니다.

문제는 서버가 SYN/ACK를 보냈는데 마지막 ACK가 돌아오지 않을 때입니다.

서버는 상대가 단순히 조금 늦게 응답하는 것인지, 패킷이 유실된 것인지 즉시 알 수 없습니다. 따라서 일정한 재전송과 대기 과정을 거칩니다.

바로 이 TCP의 특성이 SYN Flood와 연결됩니다.

SYN Flood가 발생하면 무엇이 달라질까

SYN Flood에서는 많은 SYN 요청이 서버로 들어오지만 정상적인 연결 완료가 뒤따르지 않는 패턴이 나타날 수 있습니다.

서버에서는 이런 흐름이 반복됩니다.

SYN 수신
  ↓
SYN/ACK 전송
  ↓
마지막 ACK 대기
  ↓
연결 미완료
  ↓
SYN_RECV 상태 유지

한두 개의 미완성 연결은 문제가 되지 않습니다.

하지만 새로운 SYN 요청이 처리되는 속도가 기존 미완성 연결이 정리되는 속도보다 훨씬 빨라지면 이야기가 달라집니다.

서버는 계속해서 새로운 연결 시도를 받아야 하고, 아직 Handshake를 완료하지 않은 요청도 관리해야 합니다.

결국 SYN Backlog에 압력이 커집니다.

여기서 SYN Flood가 위험해지는 이유가 나옵니다.

공격자의 목적을 애플리케이션까지 수많은 HTTP 요청을 보내는 것으로만 생각하기 쉽지만, SYN Flood는 그보다 앞선 TCP 연결 생성 과정 자체에 부담을 줄 수 있습니다.

Nginx가 HTTP 요청을 처리하기도 전에 문제가 시작될 수 있다는 뜻입니다.

CPU가 정상인데 사이트가 안 열리는 이유

실무에서 이 현상이 까다로운 이유가 있습니다.

서버 상태가 겉으로는 멀쩡해 보일 수 있기 때문입니다.

예를 들어 장애가 발생했는데 다음과 같은 상태라고 해보겠습니다.

CPU Usage       17%
Memory Usage    46%
Disk I/O        정상
Nginx Process   Running
Application     Running

모니터링 화면만 보면 서버 자체에는 큰 문제가 없어 보입니다.

하지만 사용자는 사이트 접속에 실패합니다.

이 상태에서 Nginx access log만 계속 확인하면 이상한 점을 발견할 수 있습니다.

실패한 사용자 요청이 로그에 아예 나타나지 않을 수 있기 때문입니다.

TCP Handshake가 완료되지 않았다면 아직 정상적인 HTTP 요청이 Nginx까지 넘어오지 않았기 때문입니다.

따라서 이런 장애에서는 질문을 바꿔야 합니다.

“웹서버가 요청을 왜 처리하지 못했을까?”

보다 먼저,

“TCP Connection 자체가 정상적으로 만들어지고 있는가?”

를 확인해야 합니다.

가장 먼저 SYN_RECV 상태를 확인한다

Linux에서는 ss 명령으로 TCP 연결 상태를 확인할 수 있습니다.

현재 SYN_RECV 상태의 연결을 확인하려면 다음과 같이 실행합니다.

ss -ant state syn-recv

개수만 빠르게 보고 싶다면 헤더를 제외하고 셀 수 있습니다.

ss -Hant state syn-recv | wc -l

여기서 숫자가 높다고 바로 공격이라고 결론 내리면 안 됩니다.

예를 들어 평소 SYN_RECV가 수십 개 정도였던 서버에서 갑자기 수천 개로 증가했다면 조사할 만한 신호가 됩니다.

하지만 평상시에도 신규 연결량이 매우 많은 서비스라면 순간적으로 높은 숫자가 나타날 수 있습니다.

절대적인 숫자 하나보다 중요한 것은 평소 기준값과 변화 폭입니다.

평상시 SYN_RECV
        ↓
현재 SYN_RECV
        ↓
동일 시간대 신규 Connection 수
        ↓
SYN 유입량
        ↓
Handshake 완료 여부

이런 식으로 연결해서 봐야 합니다.

tcp_max_syn_backlog 값도 확인한다

SYN 관련 현상을 분석할 때 같이 확인할 값이 있습니다.

sysctl net.ipv4.tcp_max_syn_backlog

또는 다음처럼 확인할 수도 있습니다.

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

tcp_max_syn_backlog는 아직 클라이언트의 ACK를 받지 못한 연결 요청을 기억하는 최대 수와 관련된 커널 설정입니다.

여기서 자주 하는 실수가 있습니다.

장애가 발생하자마자 이 값을 크게 올리는 것입니다.

SYN Queue 문제 발생
        ↓
tcp_max_syn_backlog 증가
        ↓
문제 해결?

항상 그렇지는 않습니다.

정상적인 접속 증가 때문에 일시적으로 대기열이 부족한 것이라면 시스템 특성과 메모리, 서비스 규모를 고려한 튜닝이 도움이 될 수 있습니다.

하지만 계속해서 비정상적인 SYN이 들어오는 상황이라면 Queue 크기만 키우는 것은 결국 더 많은 요청을 잠시 담아두는 것에 가깝습니다.

원인을 확인하지 않고 커널 값을 먼저 변경해서는 안 되는 이유입니다.

SYN Cookies는 언제 등장할까

Linux에서 SYN Flood와 함께 자주 나오는 기능이 SYN Cookies입니다.

설정은 다음과 같이 확인할 수 있습니다.

sysctl net.ipv4.tcp_syncookies

일반적으로 다음과 같이 표시될 수 있습니다.

net.ipv4.tcp_syncookies = 1

여기서 1이라는 값을 보고 “모든 TCP 연결이 SYN Cookie 방식으로 처리되는구나”라고 이해하면 안 됩니다.

Linux에서 일반적인 tcp_syncookies=1 설정은 SYN Backlog가 overflow되는 상황에서 SYN Cookies를 사용하는 방어 메커니즘입니다.

핵심 아이디어는 서버가 미완성 연결 상태를 일반적인 방식으로 계속 보관하지 않고도, 이후 돌아오는 ACK를 검증해 연결을 이어갈 수 있도록 하는 것입니다.

따라서 SYN Queue에 과도한 압력이 발생했을 때 정상 연결을 계속 받을 가능성을 높여줍니다.

하지만 SYN Cookies를 대규모 정상 접속을 처리하기 위한 일반적인 성능 확장 방법으로 생각해서는 안 됩니다.

Linux Kernel 문서에서도 syncookies는 정상적인 과부하를 해결하기 위한 수단이라기보다 SYN Flood 상황을 위한 fallback 메커니즘으로 설명합니다.

SYN Cookies가 실제로 동작했는지도 확인해보자

설정이 켜져 있다는 사실과 실제로 SYN Cookies가 사용됐다는 것은 다른 이야기입니다.

서버에서 네트워크 통계를 확인하면 판단에 도움이 됩니다.

환경에 nstat이 있다면 다음과 같은 항목을 살펴볼 수 있습니다.

nstat -az | grep -E 'Syncookies|ListenOverflows|ListenDrops'

여기서 중요한 것은 특정 숫자 하나가 아니라 장애 발생 시간대에 카운터가 얼마나 증가했는지입니다.

예를 들어 평상시 거의 움직이지 않던 Syncookies 관련 카운터가 접속 장애가 발생한 순간 빠르게 증가했다면 SYN Backlog 압력이 실제로 발생했을 가능성을 생각해볼 수 있습니다.

이런 식으로 상태와 통계를 연결해야 합니다.

SYN_RECV 급증

        +

Syncookies 관련 카운터 증가

        +

신규 Connection 실패

        ↓

SYN 처리 구간 조사 필요

이 정도까지 확인하면 단순히 “사이트가 느리다”는 수준보다 원인 범위를 상당히 좁힐 수 있습니다.

실제 SYN 패킷을 확인하는 방법

상태값만으로 판단하기 어려울 때는 실제 패킷 흐름도 확인할 수 있습니다.

예를 들어 ACK가 설정되지 않은 최초 SYN 패킷을 확인하려면 다음과 같은 필터를 사용할 수 있습니다.

tcpdump -nn -i any 'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'

HTTPS 서비스만 보고 싶다면 포트 조건도 추가할 수 있습니다.

tcpdump -nn -i any 'tcp port 443 and tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'

운영 서버에서는 주의할 점이 있습니다.

트래픽이 많은 서버에서 tcpdump를 조건 없이 오래 실행하면 출력량이 지나치게 커질 수 있습니다. 장애 분석에 필요한 인터페이스와 포트, 실행 시간을 제한해서 확인하는 편이 안전합니다.

패킷을 볼 때는 단순히 “SYN이 많다”만 확인하는 것이 아닙니다.

SYN 증가 시점, 출발지 분포, 같은 시점의 정상 연결 완료 여부 등을 함께 봐야 합니다.

SYN_RECV가 많으면 무조건 SYN Flood일까

아닙니다.

이 부분은 서버 장애 분석에서 특히 중요합니다.

정상적인 서비스에서도 짧은 시간에 신규 Connection이 폭증할 수 있습니다.

대표적인 상황이 있습니다.

서비스 오픈 직후 사용자가 한꺼번에 접속했거나, 대규모 이벤트가 시작됐거나, 외부 시스템이 동시에 많은 연결을 만들었거나, 클라이언트 네트워크 상태가 좋지 않아 연결 완료가 늦어질 수도 있습니다.

그래서 SYN_RECV 증가 하나만으로 공격이라고 단정하면 안 됩니다.

예를 들어 이런 상황을 비교해볼 수 있습니다.

[정상적인 접속 폭증]

SYN 증가
   ↓
SYN/ACK 응답
   ↓
ACK 정상 반환
   ↓
Connection 다수 성립
   ↓
HTTP 요청 증가

반대로 조사 과정에서 다음과 같은 모습이 나타난다면 조금 다르게 봐야 합니다.

[비정상적인 연결 시도 의심]

SYN 대량 증가
   ↓
SYN_RECV 급증
   ↓
Handshake 완료율 저하
   ↓
정상 신규 Connection 영향
   ↓
SYN 관련 커널 통계 변화

결국 공격 여부는 하나의 명령어 출력이 아니라 여러 지표를 조합해서 판단해야 합니다.

실무에서는 이런 순서로 원인을 좁힌다

실제 장애 상황을 하나 가정해보겠습니다.

오전 10시부터 일부 사용자가 HTTPS 접속에 실패하기 시작했습니다.

그런데 서버 모니터링에서는 CPU와 메모리 사용률이 평소와 크게 다르지 않았습니다.

우선 Nginx 프로세스를 확인합니다.

systemctl status nginx

프로세스는 정상입니다.

이때 곧바로 Nginx를 재시작하기보다 TCP 상태를 확인합니다.

ss -s

그리고 SYN_RECV를 따로 확인합니다.

ss -Hant state syn-recv | wc -l

평상시와 비교했을 때 SYN_RECV가 크게 늘었다면 다음 단계로 넘어갑니다.

sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.ipv4.tcp_syncookies

이후 네트워크 통계와 실제 패킷 흐름을 확인합니다.

분석 흐름을 정리하면 다음과 같습니다.

사용자 접속 실패
       ↓
CPU / Memory 확인
       ↓
Application Process 확인
       ↓
TCP 상태 확인
       ↓
SYN_RECV 증가 확인
       ↓
SYN 관련 Kernel 설정 확인
       ↓
Kernel Network Counter 확인
       ↓
실제 SYN 트래픽 확인
       ↓
정상 폭증 / 네트워크 문제 / 공격 가능성 구분

이 과정에서 중요한 것은 처음부터 원인을 SYN Flood라고 정해놓지 않는 것입니다.

관찰된 증거를 하나씩 연결하면서 가능성을 좁혀야 합니다.

대응도 Queue 크기 조정부터 시작하면 안 된다

SYN 관련 문제가 확인됐다고 해서 첫 번째 대응이 무조건 tcp_max_syn_backlog 증가일 필요는 없습니다.

정상 트래픽이 순간적으로 늘어난 것인지, 특정 네트워크 구간에서 문제가 발생한 것인지, 실제로 비정상적인 패킷이 지속적으로 들어오는 것인지 먼저 구분해야 합니다.

공격성 트래픽이 확인된다면 서버 한 대의 커널 파라미터만으로 해결하려 하지 않는 편이 좋습니다.

환경에 따라 방화벽, 로드밸런서, CDN, 클라우드 사업자의 DDoS 방어 기능 등 서버보다 앞단에서 트래픽을 걸러내는 구조가 필요할 수 있습니다.

왜냐하면 서버까지 공격 패킷이 대량으로 도착한 뒤 처리하는 것보다 가능한 앞단에서 비정상 트래픽을 제어하는 것이 더 효과적인 상황이 있기 때문입니다.

반대로 공격이 아니라 정상 사용자의 급격한 증가가 원인이라면 이야기가 달라집니다.

그때는 Connection 처리량, Backlog 설정, 애플리케이션 accept 처리 속도, File Descriptor, Nginx Worker 설정 등을 전체적으로 점검해야 합니다.

같은 “접속 실패”라도 원인에 따라 해결책이 완전히 달라집니다.

SYN Queue와 Accept Queue 문제를 구분해야 한다

마지막으로 한 가지는 확실히 구분해둘 필요가 있습니다.

SYN Queue 쪽이 밀리는 것과 연결이 완료된 뒤 애플리케이션의 accept() 처리가 밀리는 것은 다른 문제입니다.

Client
  │
  │ SYN
  ▼
[미완성 연결 영역]
  │
  │ Handshake 완료
  ▼
[accept() 대기 영역]
  │
  │ accept()
  ▼
Application

앞쪽에서 문제가 발생했다면 SYN_RECV, SYN Backlog, 패킷 유입 상태 등을 먼저 봐야 합니다.

뒤쪽에서 문제가 발생했다면 웹서버나 애플리케이션이 Connection을 얼마나 빠르게 accept하고 있는지, 프로세스 상태나 File Descriptor가 부족하지 않은지를 확인해야 합니다.

사용자에게는 둘 다 단순히 “사이트 접속이 안 된다”로 보입니다.

운영자에게 필요한 것은 접속 실패가 TCP 처리 과정의 어느 위치에서 발생했는지를 찾아내는 것입니다.

정리

SYN Flood의 핵심은 패킷 개수 자체보다 TCP 연결이 완성되기 전에 서버가 관리해야 하는 상태와 관련이 있습니다.

정상적인 클라이언트라면 SYN을 보낸 뒤 3-Way Handshake를 빠르게 완료하지만, 많은 연결이 완료되지 않은 채 쌓이기 시작하면 SYN Backlog에 압력이 발생할 수 있습니다.

이때 서버의 CPU와 메모리가 정상이라고 해서 네트워크 계층까지 정상이라고 판단해서는 안 됩니다.

ss를 이용한 SYN_RECV 확인, tcp_max_syn_backlog와 tcp_syncookies 확인, 네트워크 통계 변화, 실제 SYN 패킷 흐름을 차례대로 살펴보면 문제 발생 위치를 훨씬 구체적으로 좁힐 수 있습니다.

그리고 가장 중요한 원칙이 하나 있습니다.

SYN_RECV가 많다는 이유만으로 SYN Flood라고 단정하지 않는 것입니다.

정상적인 접속 폭증에서도 비슷한 현상이 나타날 수 있기 때문에 평상시 Connection 패턴과 장애 시점의 변화, Handshake 완료 상태, 커널 통계, 실제 패킷을 함께 비교해야 합니다.

서버 장애 분석은 특정 숫자가 높다는 사실을 찾는 것으로 끝나지 않습니다.

그 숫자가 왜 높아졌는지를 TCP 연결 흐름 안에서 설명할 수 있어야 실제 원인에 가까워집니다.

instone
함께 보면 좋은 글

댓글 0

첫 댓글을 남겨보세요.

error: Content is protected !!

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.