SCTP 프로토콜

Linux SCTP(Stream Control Transmission Protocol) 구현을 심층 설명합니다. 멀티스트리밍·멀티호밍 기반 Association 모델, 초기 4-way handshake, Path 관리와 failover, 메시지 단위 전송 보장, TCP/UDP 대비 설계 차이, 커널 자료구조와 타이머 동작, 통신 장애·재전송·경로 전환 이슈에 대한 운영 디버깅 포인트까지 다룹니다.

전제 조건: 네트워크 스택IP 프로토콜 문서를 먼저 읽으세요. 전송 계층은 재전송, 흐름 제어, 연결 상태 전이가 핵심이므로 패킷 생명주기를 먼저 잡아야 디버깅이 쉬워집니다.
일상 비유: 이 주제는 배송 접수와 운송 계약과 비슷합니다. TCP는 계약형 운송, UDP는 즉시 발송, SCTP는 다중 경로 옵션을 가진 운송처럼 보면 동작 차이를 빠르게 비교할 수 있습니다.

핵심 요약

  • Association — TCP 연결과 유사하지만 쿠키 기반 4-way handshake
  • Multi-streaming — 독립 스트림으로 HoL blocking 완화
  • Multi-homing — 다중 경로 + heartbeat failover
  • Chunk — 제어/데이터를 하나의 패킷 구조로 구성
  • WebRTC DataChannel — 실무 대표 활용 사례

단계별 이해

  1. 연결 모델
    4-way handshake와 state cookie 방식을 먼저 이해합니다.
  2. 스트림 모델
    스트림 번호 기반 데이터 분리를 확인합니다.
  3. 경로 모델
    멀티호밍에서 primary/backup 경로 전환을 살펴봅니다.
  4. 운영 모델
    sysctl과 소켓 옵션으로 타임아웃/재전송 정책을 조정합니다.
관련 표준: RFC 793 (TCP), RFC 768 (UDP), RFC 4960 (SCTP) — 전송 계층 프로토콜 표준입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

SCTP (Stream Control Transmission Protocol)

SCTP(IP 프로토콜 132)는 TCP의 신뢰성과 UDP의 메시지 경계 보존을 결합한 전송 프로토콜입니다. 원래 SIGTRAN(SS7 시그널링 전송)을 위해 설계되었으나, 범용 전송 프로토콜로 발전하여 RFC 4960으로 표준화되었습니다. 멀티스트리밍, 멀티호밍, 4-way handshake, 메시지 지향 전달 등 TCP와 UDP 모두에 없는 고유 기능을 제공합니다.

리눅스 커널은 net/sctp/ 디렉터리에 SCTP 프로토콜 스택을 구현하며, CONFIG_IP_SCTP 커널 설정으로 빌드합니다. 모듈 이름은 sctp이고, 사용자 공간에서는 lksctp-tools 패키지가 헤더 파일과 유틸리티를 제공합니다.

# SCTP 커널 모듈 로드
$ modprobe sctp

# 모듈 확인
$ lsmod | grep sctp
sctp                  413696  0
libcrc32c              16384  2 sctp,nf_conntrack

# lksctp-tools 설치 (Debian/Ubuntu)
$ apt-get install lksctp-tools libsctp-dev

# lksctp-tools 설치 (RHEL/CentOS)
$ yum install lksctp-tools lksctp-tools-devel
SCTP 프로토콜 번호: SCTP는 IP 프로토콜 번호 132를 사용합니다. TCP(6)나 UDP(17)와 달리 별도의 프로토콜 번호를 가지므로, 방화벽과 NAT 장비에서 SCTP를 명시적으로 허용해야 합니다. iptables -A INPUT -p sctp --dport 9999 -j ACCEPT

SCTP의 역사는 1990년대 후반 IETF SIGTRAN 워킹 그룹에서 시작됩니다. 기존 SS7 시그널링을 IP 네트워크로 전송하기 위해 TCP와 UDP 모두 부적합했습니다. TCP는 HoL blocking과 단일 경로 문제가 있었고, UDP는 신뢰성이 없었습니다. 이 요구사항에서 SCTP가 탄생했으며, 2000년 RFC 2960(이후 RFC 4960으로 갱신)으로 표준화되었습니다.

연도RFC내용
2000RFC 2960SCTP 최초 표준화
2004RFC 3758PR-SCTP (부분 신뢰성) 확장
2004RFC 3873SCTP MIB (관리 정보 베이스)
2007RFC 4820Padding Chunk
2007RFC 4895AUTH 청크 (인증)
2007RFC 4960SCTP 개정판 (현재 주요 표준)
2007RFC 5061동적 주소 재설정 (ASCONF)
2011RFC 6458소켓 API 확장 (sendv/recvv)
2013RFC 6951UDP 캡슐화 (NAT 통과)
2017RFC 8260스트림 스케줄러 및 메시지 인터리빙
/* SCTP 프로토콜 등록 — net/sctp/protocol.c */
static const struct net_protocol sctp_protocol = {
    .handler     = sctp_rcv,       /* 패킷 수신 핸들러 */
    .err_handler = sctp_v4_err,    /* ICMP 오류 핸들러 */
    .no_policy   = 1,              /* xfrm 정책 미적용 */
};

/* 모듈 초기화 — sctp_init() */
static int __init sctp_init(void)
{
    /* 1. 프로토콜 구조체 등록 */
    inet_add_protocol(&sctp_protocol, IPPROTO_SCTP);

    /* 2. 소켓 ops 등록 (SOCK_STREAM, SOCK_SEQPACKET) */
    inet_register_protosw(&sctp_stream_protosw);
    inet_register_protosw(&sctp_seqpacket_protosw);

    /* 3. 해시 테이블 초기화 (포트, 엔드포인트 검색용) */
    sctp_hash_init();

    /* 4. sysctl 등록 */
    sctp_sysctl_register();

    /* 5. proc 파일시스템 등록 */
    sctp_proc_init();

    return 0;
}
SCTP 패킷 구조 IP 헤더 (Protocol = 132) SCTP 공통 헤더 (12 바이트) Source Port Destination Port Verification Tag Checksum 청크 1 (DATA) Type | Flags | Length | TSN | Stream ID | SSN | PPID | Data... 청크 2 (SACK) Type | Flags | Length | Cum TSN Ack | a_rwnd | Gap Blocks... 하나의 SCTP 패킷에 여러 청크를 번들링 가능 (Chunk Bundling) Verification Tag: Association 식별, Checksum: CRC32c (RFC 3309)

SCTP 핵심 특성

SCTP는 TCP와 UDP의 장점을 결합하면서도 각각의 한계를 극복하도록 설계되었습니다. 다음 표는 세 프로토콜의 핵심 특성을 비교합니다.

특성TCPUDPSCTP
연결 지향 O X O (Association)
신뢰적 전달 O X O (선택적 비순서 전달도 가능)
메시지 경계 보존 X (바이트 스트림) O O (청크 단위)
멀티스트리밍 X X O (독립 스트림, HoL blocking 방지)
멀티호밍 X X O (다수 IP 주소 바인딩, failover)
SYN Flood 방어 SYN Cookie 해당 없음 4-way handshake + Cookie (내장)
부분 신뢰성 X 해당 없음 O (PR-SCTP, RFC 3758)
Handshake 3-way 없음 4-way (쿠키 기반)
종료 4-way (FIN/ACK) 없음 3-way (SHUTDOWN)
확인응답 Byte 기반 ACK 없음 TSN 기반 SACK
혼잡 제어 단일 경로 없음 경로별 독립 혼잡 제어
Association vs Connection: SCTP에서는 연결을 "Association"이라 부릅니다. TCP 연결은 (src IP, src port, dst IP, dst port) 4-튜플로 식별하지만, SCTP Association은 양쪽 모두 여러 IP를 가질 수 있으므로 Verification Tag로 식별합니다. 하나의 Association 내에 여러 스트림이 존재하는 점도 TCP와의 근본적 차이입니다.

SCTP 데이터 흐름

SCTP 데이터 전송은 애플리케이션 메시지를 청크(Chunk) 단위로 분할하고, TSN(Transmission Sequence Number)을 부여하여 신뢰적으로 전달합니다. 수신 측은 SACK 청크로 수신 상태를 알려주며, 송신 측은 이를 기반으로 재전송을 결정합니다.

SCTP 데이터 전송 흐름 송신 애플리케이션 sctp_sendmsg() 단편화 (Fragmentation) DATA 청크 + TSN 부여 번들링 (Bundling) 네트워크 (IP Protocol 132) 수신 애플리케이션 sctp_recvmsg() 재조립 (Reassembly) SACK 생성 SACK (확인응답) Gap Report 기반 재전송 (Fast Retransmit)

위 다이어그램에서 핵심 포인트는 다음과 같습니다:

SCTP Association과 4-Way Handshake

SCTP Association은 TCP 연결에 해당하는 개념으로, 양쪽 엔드포인트 간의 통신 채널을 나타냅니다. TCP의 3-way handshake와 달리 SCTP는 4-way handshake를 사용하여 DoS(Denial-of-Service) 공격에 대한 근본적인 방어를 제공합니다.

핵심 차이점은 서버가 INIT-ACK를 보낼 때 어떤 상태도 저장하지 않는다는 것입니다. 대신 Association 설정에 필요한 모든 정보를 State Cookie에 HMAC 서명과 함께 인코딩하여 클라이언트에게 돌려보냅니다. 클라이언트가 COOKIE-ECHO로 이 쿠키를 되돌려보내야만 서버가 상태를 할당합니다.

SCTP 4-Way Handshake 클라이언트 (CLOSED → COOKIE_WAIT) 서버 (LISTEN → ESTABLISHED) 1) INIT Initiate Tag, OS/MIS, a-rwnd, 주소 목록 2) INIT-ACK Initiate Tag, OS/MIS, a-rwnd, State Cookie (HMAC 서명) 3) COOKIE-ECHO State Cookie 반환 (+ DATA 청크 포함 가능) 4) COOKIE-ACK Cookie HMAC 검증 성공 → Association 생성 COOKIE_WAIT COOKIE_ECHOED ESTABLISHED 상태 없음 상태 없음 ESTABLISHED 서버는 3단계까지 상태를 저장하지 않음
단계방향청크포함 정보서버 상태
1 클라이언트 → 서버 INIT Initiate Tag, OS, MIS, a-rwnd, 주소 목록 상태 없음
2 서버 → 클라이언트 INIT-ACK Initiate Tag, OS, MIS, a-rwnd, State Cookie 상태 없음 (Cookie에 인코딩)
3 클라이언트 → 서버 COOKIE-ECHO State Cookie (+ DATA 가능) Cookie 검증 중
4 서버 → 클라이언트 COOKIE-ACK 검증 완료 확인 ESTABLISHED
/* SCTP 4-way handshake (INIT → INIT-ACK → COOKIE-ECHO → COOKIE-ACK) */
/*
 * 1. 클라이언트 → INIT 청크 (자신의 Tag, 스트림 수, 주소 목록)
 * 2. 서버 → INIT-ACK 청크 (자신의 Tag + State Cookie)
 *    → 서버는 이 시점에서 상태를 저장하지 않음 (SYN Flood 면역)
 *    → State Cookie에 검증 정보를 HMAC으로 서명하여 인코딩
 * 3. 클라이언트 → COOKIE-ECHO (State Cookie 그대로 반환)
 * 4. 서버 → COOKIE-ACK (Cookie 검증 후 Association 생성)
 *    → 3단계에서 이미 데이터 포함 가능 (TCP Fast Open과 유사)
 */

/* include/net/sctp/structs.h */
struct sctp_association {
    struct sctp_ep_common base;
    struct list_head transports;     /* 원격 주소 목록 (멀티호밍) */
    struct sctp_stream stream;       /* 스트림 관리 */
    __u16 c.sinit_num_ostreams;      /* 출력 스트림 수 */
    __u16 c.sinit_max_instreams;     /* 입력 스트림 수 */
    __u32 c.my_vtag;                 /* 자신의 Verification Tag */
    __u32 c.peer_vtag;               /* 상대의 Verification Tag */
    /* ... */
};
/* State Cookie 생성 과정 — net/sctp/sm_make_chunk.c */
/* sctp_pack_cookie():
 *   1. 양쪽 INIT 파라미터를 cookie에 직렬화
 *   2. 타임스탬프 + Association 설정 정보 포함
 *   3. HMAC-SHA1으로 서명 (sctp_cookie->hmac)
 *   4. 선택적으로 AES-CBC 암호화 가능
 */

static struct sctp_cookie *sctp_pack_cookie(
    const struct sctp_endpoint *ep,
    const struct sctp_association *asoc,
    const struct sctp_chunk *init_chunk,
    int *cookie_len)
{
    struct sctp_signed_cookie *cookie;
    int headersize, bodysize;

    headersize = sizeof(struct sctp_paramhdr) +
                 sizeof(struct sctp_signed_cookie);
    bodysize = ntohs(init_chunk->chunk_hdr->length);

    /* HMAC 서명 — endpoint의 secret_key 사용 */
    sctp_hash_digest(ep->secret_key, SCTP_SECRET_SIZE,
                     (__u8 *)cookie, bodysize,
                     cookie->signature);
    return cookie;
}
State Cookie 수명: State Cookie에는 타임스탬프가 포함되어 있으며, net.sctp.valid_cookie_life (기본 60초) 이내에 COOKIE-ECHO가 도착해야 합니다. 만료된 쿠키로 COOKIE-ECHO를 보내면 서버는 ABORT를 전송합니다. 고지연 네트워크에서는 이 값을 늘릴 필요가 있습니다.

SCTP의 4-way handshake는 TCP의 3-way handshake보다 한 단계가 더 필요하지만, 이로 인해 서버 측에서 SYN Flood와 같은 자원 고갈 공격에 근본적으로 면역이 됩니다. TCP에서는 SYN Cookie라는 추가 메커니즘으로 이 문제를 완화하지만, SCTP는 프로토콜 자체에 이 보호가 내장되어 있습니다.

공격 유형TCP 대응SCTP 대응
SYN Flood (반개방 연결) SYN Cookie (추가 구현 필요) 서버 무상태 → 근본적 면역
RST 공격 취약 (시퀀스 번호 추측) Verification Tag 검증으로 방어
연결 하이재킹 시퀀스 번호 추측으로 가능 32비트 Verification Tag + CRC32c
ICMP 공격 ICMP로 연결 방해 가능 Verification Tag로 ICMP 검증
/* Association 종료 과정 — 3-way SHUTDOWN */
/*
 * TCP 종료 (4-way):               SCTP 종료 (3-way):
 *   FIN →                           SHUTDOWN →
 *   ← ACK                           ← SHUTDOWN-ACK
 *   ← FIN                           SHUTDOWN-COMPLETE →
 *   ACK →
 *   (TIME_WAIT 2*MSL 대기)          (즉시 CLOSED)
 *
 * SCTP 장점: TIME_WAIT 상태 없음 → 포트 즉시 재사용 가능
 */

/* 정상 종료: close() 호출 시 */
/* net/sctp/socket.c — sctp_close():
 *   1. SHUTDOWN_PENDING 상태로 전이
 *   2. 미전송 DATA 청크가 모두 확인될 때까지 대기
 *   3. SHUTDOWN 청크 전송 (CumTSN 포함)
 *   4. SHUTDOWN-ACK 수신 시 SHUTDOWN-COMPLETE 전송
 *   5. Association 즉시 해제 (TIME_WAIT 없음)
 */

/* 비정상 종료: ABORT 전송 */
struct sctp_sndrcvinfo sinfo;
sinfo.sinfo_flags = SCTP_ABORT;   /* ABORT 플래그 */
sinfo.sinfo_assoc_id = assoc_id;
sctp_send(fd, NULL, 0, &sinfo, 0);
/* → Association 즉시 종료, 미전송 데이터 폐기 */
Graceful vs Ungraceful 종료: close()는 미전송 데이터를 모두 보낸 후 SHUTDOWN 시퀀스를 수행합니다 (graceful). SCTP_ABORT 플래그는 즉시 ABORT 청크를 보내고 Association을 파괴합니다 (ungraceful). SO_LINGER 옵션으로 graceful 종료의 대기 시간을 제한할 수 있습니다.

멀티스트리밍

멀티스트리밍은 SCTP의 가장 혁신적인 기능 중 하나입니다. 하나의 Association 내에 여러 독립적인 스트림을 두어, 한 스트림에서 패킷 손실이 발생해도 다른 스트림의 데이터 전달에 영향을 주지 않습니다. 이는 TCP에서 발생하는 Head-of-Line(HoL) blocking 문제를 프로토콜 수준에서 해결합니다.

SCTP 멀티스트리밍: 독립 스트림과 HoL Blocking 방지 송신 애플리케이션 Stream 0 Stream 1 Stream 2 Association (단일 SCTP 연결) Stream 0 Stream 1 Stream 2 수신 애플리케이션 TCP: Head-of-Line Blocking Stream A 손실 → Stream B, C도 대기 모든 데이터가 단일 바이트 스트림에 섞임 SCTP: 독립 스트림 Stream 0 손실 → Stream 1, 2는 정상 전달 각 스트림별 독립적 순서 보장 스트림 스케줄러 (RFC 8260) SCTP_SS_FCFS SCTP_SS_RR SCTP_SS_RR_PKT SCTP_SS_PRIO SCTP_SS_FC

각 스트림은 자체적인 SSN(Stream Sequence Number)을 유지합니다. 스트림 내에서는 순서가 보장되지만, 서로 다른 스트림 간에는 순서 관계가 없습니다. 또한 SCTP_UNORDERED 플래그를 사용하면 스트림 내에서도 비순서 전달이 가능합니다.

스트림 스케줄러소켓 옵션 값동작사용 사례
FCFS SCTP_SS_FCFS 선착순 (기본값) 일반적인 사용
Round Robin SCTP_SS_RR 스트림 단위 라운드 로빈 공정한 대역폭 분배
RR Packet SCTP_SS_RR_PKT 패킷 단위 라운드 로빈 세밀한 공정성
Priority SCTP_SS_PRIO 스트림별 우선순위 제어 vs 데이터 분리
Fair Capacity SCTP_SS_FC 큐잉 비율 기반 공정 분배 멀티미디어 스트리밍
/* SCTP 멀티스트리밍: 하나의 Association 내에 독립적인 스트림들 */
/* 장점: 한 스트림의 패킷 손실이 다른 스트림에 영향을 주지 않음
 *       → HTTP/2의 Head-of-Line blocking 문제를 프로토콜 수준에서 해결
 */

/* 사용자 공간: sctp_sendmsg로 스트림 지정 */
struct sctp_sndrcvinfo sinfo = {
    .sinfo_stream = 3,              /* 스트림 번호 3으로 전송 */
    .sinfo_flags  = SCTP_UNORDERED, /* 비순서 전달 (선택) */
    .sinfo_ppid   = htonl(42),     /* Payload Protocol ID */
};
sctp_sendmsg(fd, data, len, NULL, 0,
             sinfo.sinfo_ppid, sinfo.sinfo_flags,
             sinfo.sinfo_stream, 0, 0);

/* 수신: sctp_recvmsg로 스트림 정보 확인 */
struct sctp_sndrcvinfo rinfo;
int flags = 0;
sctp_recvmsg(fd, buf, buflen, NULL, 0, &rinfo, &flags);
printf("stream=%u ppid=%u\\n", rinfo.sinfo_stream, ntohl(rinfo.sinfo_ppid));
/* 스트림 스케줄러 설정 — RFC 8260 */
struct sctp_assoc_value av;
av.assoc_id = 0;
av.assoc_value = SCTP_SS_PRIO;  /* 우선순위 기반 스케줄러 */
setsockopt(fd, IPPROTO_SCTP, SCTP_STREAM_SCHEDULER, &av, sizeof(av));

/* 스트림 0에 높은 우선순위 부여 (제어 메시지용) */
struct sctp_stream_value sv;
sv.assoc_id = 0;
sv.stream_id = 0;
sv.stream_value = 0;  /* 값이 낮을수록 높은 우선순위 */
setsockopt(fd, IPPROTO_SCTP, SCTP_STREAM_SCHEDULER_VALUE, &sv, sizeof(sv));

/* 스트림 1에 낮은 우선순위 부여 (데이터 전송용) */
sv.stream_id = 1;
sv.stream_value = 10;
setsockopt(fd, IPPROTO_SCTP, SCTP_STREAM_SCHEDULER_VALUE, &sv, sizeof(sv));
/* 커널 내부: 스트림 관리 구조체 — include/net/sctp/structs.h */
struct sctp_stream {
    struct {
        struct sctp_stream_out_ext *ext;
        __u16 ssn;       /* Stream Sequence Number */
        __u8  state;     /* 스트림 상태 */
    } *out;                  /* 출력 스트림 배열 */
    struct {
        __u16 ssn;       /* 수신 SSN */
        __u16 mid;       /* Message ID (I-DATA용) */
    } *in;                   /* 입력 스트림 배열 */
    __u16 outcnt;             /* 출력 스트림 수 */
    __u16 incnt;              /* 입력 스트림 수 */
    struct sctp_stream_interleave *si;  /* 인터리빙 핸들러 */
};

멀티호밍과 Failover

SCTP의 멀티호밍은 하나의 Association에서 양쪽 엔드포인트가 각각 여러 IP 주소를 가질 수 있는 기능입니다. INIT/INIT-ACK 교환 시 양쪽이 자신의 주소 목록을 알려주고, 이후 Heartbeat 메커니즘으로 각 경로의 활성 상태를 확인합니다. Primary 경로에 장애가 발생하면 자동으로 보조(Alternate) 경로로 전환합니다.

SCTP 멀티호밍: 장애 감지와 경로 전환 클라이언트 NIC0: 10.0.0.1 NIC1: 10.0.1.1 서버 NIC0: 10.0.0.2 NIC1: 10.0.1.2 Primary 경로 (DATA) 보조 경로 (Heartbeat) 장애 발생 장애 감지 과정 DATA 재전송 실패 → error_count++ → path_max_retrans 초과 → SCTP_TRANSPORT_DOWN NIC0: 10.0.0.1 (DOWN) NIC1: 10.0.1.1 (Active) NIC0: 10.0.0.2 (DOWN) NIC1: 10.0.1.2 (Active) 새로운 Primary 경로 (자동 전환) 경로 복구 과정 Heartbeat-ACK 수신 → error_count 초기화 → SCTP_TRANSPORT_UP → primary 복원 가능
경로 상태상수설명전환 조건
Active SCTP_ACTIVE 정상 사용 가능 Heartbeat-ACK 수신 또는 DATA-ACK 수신
Inactive SCTP_INACTIVE 장애로 비활성화 error_count > path_max_retrans
PF (Potentially Failed) SCTP_PF 잠재적 장애 (빠른 전환용) error_count > pf_retrans
Unconfirmed SCTP_UNCONFIRMED 아직 확인되지 않은 주소 초기 상태 (INIT에서 알려진 주소)
/* SCTP 멀티호밍: 양쪽 엔드포인트가 여러 IP 주소를 가질 수 있음 */
/* → 한 경로 실패 시 자동으로 다른 경로로 전환 (heartbeat 기반) */

/* 다수 주소 바인딩 */
struct sockaddr_in addrs[2];
addrs[0].sin_addr.s_addr = inet_addr("10.0.0.1");
addrs[0].sin_port = htons(9999);
addrs[1].sin_addr.s_addr = inet_addr("10.0.1.1");
addrs[1].sin_port = htons(9999);
sctp_bindx(fd, (struct sockaddr *)addrs, 2, SCTP_BINDX_ADD_ADDR);

/* sctp_connectx: 여러 서버 주소로 동시에 연결 시도 */
struct sockaddr_in dests[2];
dests[0].sin_addr.s_addr = inet_addr("10.0.0.2");
dests[0].sin_port = htons(9999);
dests[1].sin_addr.s_addr = inet_addr("10.0.1.2");
dests[1].sin_port = htons(9999);
sctp_assoc_t assoc_id;
sctp_connectx(fd, (struct sockaddr *)dests, 2, &assoc_id);

/* Primary 주소 수동 변경 */
struct sctp_prim prim;
prim.ssp_assoc_id = assoc_id;
prim.ssp_addr = *(struct sockaddr_storage *)&dests[1];
setsockopt(fd, IPPROTO_SCTP, SCTP_PRIMARY_ADDR, &prim, sizeof(prim));
/* Heartbeat: 보조 경로의 상태를 주기적으로 확인
 *   net.sctp.hb_interval = 30000 (ms, 기본 30초)
 *   연속 path_max_retrans 회 실패 시 경로 비활성화
 *   → primary 경로 실패 시 active 보조 경로로 자동 전환
 */

/* Potentially Failed (PF) 상태 — 빠른 경로 전환 */
/* pf_retrans 값을 path_max_retrans보다 작게 설정하면
 * 경로가 완전히 DOWN되기 전에 보조 경로로 빠르게 전환 */
struct sctp_paddrparams params;
params.spp_assoc_id = assoc_id;
params.spp_address = *(struct sockaddr_storage *)&dests[0];
params.spp_pathmaxrxt = 5;    /* 5회 실패시 DOWN */
params.spp_hbinterval = 10000; /* 10초 Heartbeat */
params.spp_flags = SPP_HB_ENABLE;
setsockopt(fd, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS,
           &params, sizeof(params));
/* 커널 내부: net/sctp/transport.c */
/* sctp_assoc_control_transport():
 *   SCTP_TRANSPORT_DOWN → 해당 경로 비활성화
 *   → sctp_assoc_update_retran_path()로 재전송 경로 변경
 */

/* net/sctp/associola.c — 경로 전환 로직 */
void sctp_assoc_update_retran_path(struct sctp_association *asoc)
{
    struct sctp_transport *t, *next;
    struct list_head *head = &asoc->peer.transport_addr_list;
    struct list_head *pos;

    t = asoc->peer.retran_path;
    /* 현재 재전송 경로 다음부터 active 경로 탐색 */
    list_for_each(pos, &t->transports) {
        if (pos == head)
            continue;
        next = list_entry(pos, struct sctp_transport, transports);
        if (next->state != SCTP_UNCONFIRMED &&
            next->state != SCTP_PF) {
            asoc->peer.retran_path = next;
            return;
        }
    }
}
ASCONF를 통한 동적 주소 변경: SCTP는 Association이 활성인 상태에서 ASCONF(Address Configuration Change) 청크를 통해 동적으로 주소를 추가하거나 삭제할 수 있습니다 (RFC 5061). sctp_bindx(fd, addr, 1, SCTP_BINDX_ADD_ADDR)로 새 주소를 추가하면 커널이 자동으로 ASCONF를 전송합니다. 이 기능을 사용하려면 net.sctp.addip_enable = 1 설정이 필요합니다.

SCTP 청크 타입

SCTP 패킷은 하나 이상의 청크(Chunk)로 구성됩니다. 각 청크는 공통 헤더(Type, Flags, Length)와 가변 길이 Value로 이루어집니다. 제어 청크와 데이터 청크가 하나의 패킷에 함께 번들링될 수 있습니다.

SCTP 청크 헤더 구조 0 7 8 15 16 31 Chunk Type (8) Flags (8) Chunk Length (16) TSN (Transmission Sequence Number) — 32 비트 Stream Identifier (16) Stream Sequence Number (16) Payload Protocol Identifier (PPID) — 32 비트 User Data (가변 길이) DATA 청크 DATA 청크 Flags 비트 B(egin)=0x02: 첫 단편 E(nd)=0x01: 마지막 단편 U(nordered)=0x04: 비순서 전달
청크타입용도RFC
DATA0사용자 데이터 전달 (TSN, 스트림 번호, 시퀀스 번호 포함)4960
INIT1Association 시작 요청4960
INIT ACK2INIT 응답 + State Cookie4960
SACK3선택적 확인응답 (TCP SACK와 유사)4960
HEARTBEAT4경로 활성 확인 (멀티호밍)4960
HEARTBEAT ACK5Heartbeat 응답4960
ABORT6Association 즉시 종료4960
SHUTDOWN7정상 종료 시작4960
SHUTDOWN ACK8SHUTDOWN 확인4960
ERROR9오류 보고 (원인 코드 포함)4960
COOKIE ECHO10State Cookie 반환 (handshake 3단계)4960
COOKIE ACK11Cookie 확인 (handshake 4단계)4960
ECNE12ECN Echo4960
CWR13Congestion Window Reduced4960
SHUTDOWN COMPLETE14종료 완료4960
AUTH15인증 청크 (HMAC)4895
ASCONF ACK128주소 설정 변경 확인5061
RE-CONFIG130스트림 재설정6525
PAD132패딩4820
FORWARD TSN192수신 불필요한 TSN 건너뛰기 (부분 신뢰성)3758
ASCONF193주소 설정 변경 요청5061
I-DATA64인터리빙 데이터 (MID 기반)8260
I-FORWARD-TSN194인터리빙용 FORWARD TSN8260
/* 청크 공통 헤더 — include/uapi/linux/sctp.h */
struct sctp_chunkhdr {
    __u8  type;      /* 청크 타입 (0-255) */
    __u8  flags;     /* 타입별 플래그 */
    __be16 length;   /* 청크 전체 길이 (헤더 포함) */
};

/* DATA 청크 헤더 */
struct sctp_datahdr {
    __be32 tsn;        /* Transmission Sequence Number */
    __be16 stream;     /* Stream Identifier */
    __be16 ssn;        /* Stream Sequence Number */
    __be32 ppid;       /* Payload Protocol Identifier */
    __u8  payload[];   /* 사용자 데이터 */
};

/* INIT 청크 파라미터 */
struct sctp_inithdr {
    __be32 init_tag;         /* Initiate Tag */
    __be32 a_rwnd;           /* Advertised Receiver Window Credit */
    __be16 num_outbound_streams; /* 출력 스트림 수 */
    __be16 num_inbound_streams;  /* 입력 스트림 수 */
    __be32 initial_tsn;      /* 초기 TSN */
};
SCTP 사용 사례: 텔레콤 시그널링(Diameter, SIGTRAN), WebRTC DataChannel(SCTP over DTLS over UDP), 고가용성 클러스터 통신. 커널 모듈 sctp를 로드해야 하며, lksctp-tools 패키지가 사용자 공간 유틸리티를 제공합니다.

청크 번들링과 단편화

SCTP는 효율적인 전송을 위해 두 가지 메커니즘을 제공합니다. 번들링(Bundling)은 여러 작은 메시지를 하나의 SCTP 패킷에 묶어 전송하며, 단편화(Fragmentation)는 큰 메시지를 경로 MTU에 맞게 분할합니다.

번들링 (Bundling) Msg A (50B) Msg B (80B) Msg C (30B) 하나의 SCTP 패킷 DATA(A) DATA(B) DATA(C) TSN 1, 2, 3 각각 부여 단편화 (Fragmentation) 큰 메시지 (5000B > PMTU) Fragment 1 B=1, E=0 Fragment 2 B=0, E=0 Fragment 3 B=0, E=1 번들링 vs 단편화 비교 번들링: 여러 메시지 → 하나의 패킷 (효율) 단편화: 하나의 메시지 → 여러 패킷 (PMTU 적응) 번들링 제어 SCTP_NODELAY=1 → 번들링 비활성화 SCTP_NODELAY=0 → Nagle 유사 번들링 SCTP_MAXSEG → 최대 DATA 청크 크기 spp_pathmtu → 수동 경로 MTU 설정 단편화 동작 PMTU Discovery 자동 수행 각 단편에 독립 TSN 부여 수신 측에서 같은 SSN으로 재조립 B/E 플래그로 단편 순서 표시
/* 번들링 비활성화 (지연 민감 애플리케이션) */
int nodelay = 1;
setsockopt(fd, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay));

/* 최대 단편 크기 설정 */
struct sctp_assoc_value maxseg;
maxseg.assoc_id = 0;
maxseg.assoc_value = 1400;  /* DATA 페이로드 최대 1400 바이트 */
setsockopt(fd, IPPROTO_SCTP, SCTP_MAXSEG, &maxseg, sizeof(maxseg));

/* 커널 내부: 번들링 판단 — net/sctp/output.c */
/* sctp_packet_append_chunk():
 *   패킷에 청크를 추가할 때 남은 공간을 확인하고
 *   MTU 초과 시 새 패킷을 생성
 *   SCTP_NODELAY 설정 시 즉시 전송
 */

TSN 기반 신뢰적 전송과 SACK 처리

SCTP는 TSN(Transmission Sequence Number)을 사용하여 각 DATA 청크의 전달을 추적합니다. TCP의 바이트 기반 시퀀스 번호와 달리, SCTP는 청크 단위로 TSN을 부여합니다. 수신 측은 SACK(Selective Acknowledgement) 청크로 수신 상태를 보고합니다.

SACK 처리: Gap Report와 재전송 송신자 수신자 DATA TSN=1 DATA TSN=2 DATA TSN=3 (손실) X DATA TSN=4 DATA TSN=5 SACK: CumTSN=2, Gap=[4-5] TSN 1 TSN 2 Gap TSN 4 TSN 5 수신 버퍼 재전송: DATA TSN=3 SACK: CumTSN=5 모든 TSN 수신 완료 → Cumulative TSN Ack = 5
SACK 필드크기설명
Cumulative TSN Ack 32비트 연속으로 수신된 마지막 TSN (Gap 없는 지점)
Advertised Receiver Window (a-rwnd) 32비트 수신자의 여유 버퍼 크기
Number of Gap Ack Blocks 16비트 Gap Block 수 (비연속 수신 영역)
Number of Dup TSNs 16비트 중복 수신된 TSN 수
Gap Ack Block Start/End 16비트 x 2 CumTSN 기준 상대 오프셋 (시작-끝)
/* SACK 청크 구조체 — include/uapi/linux/sctp.h */
struct sctp_sackhdr {
    __be32 cum_tsn_ack;     /* 연속 확인된 마지막 TSN */
    __be32 a_rwnd;          /* 수신 윈도우 크기 */
    __be16 num_gap_blocks;  /* Gap Ack Block 수 */
    __be16 num_dup_tsns;    /* 중복 TSN 수 */
};

/* Gap Ack Block */
struct sctp_gap_ack_block {
    __be16 start;   /* CumTSN 기준 시작 오프셋 */
    __be16 end;     /* CumTSN 기준 끝 오프셋 */
};

/* 재전송 판단 — net/sctp/outqueue.c */
/* sctp_outq_sack():
 *   1. Cumulative TSN Ack까지의 청크를 전송 큐에서 제거
 *   2. Gap Ack Block에 포함되지 않는 TSN을 재전송 후보로 표시
 *   3. 3번 Gap Report가 누적되면 Fast Retransmit 수행
 *   4. a_rwnd 업데이트하여 흐름 제어에 반영
 */
SACK 지연 전송: 수신자는 모든 DATA 청크에 즉시 SACK를 보내지 않습니다. net.sctp.sack_timeout (기본 200ms) 동안 기다려서 여러 DATA 청크에 대해 하나의 SACK를 보냅니다. 단, 2개 이상의 패킷이 도착하면 즉시 SACK를 전송합니다.

SCTP 상태 머신

SCTP Association은 13개의 상태를 거칩니다. TCP의 상태 머신과 유사하지만, 4-way handshake에 대응하는 COOKIE_WAIT와 COOKIE_ECHOED 상태가 추가됩니다. 종료 과정은 TCP의 4-way FIN 교환 대신 3-way SHUTDOWN 교환을 사용합니다.

SCTP Association 상태 머신 CLOSED 클라이언트 COOKIE_WAIT send INIT COOKIE_ECHOED rcv INIT-ACK 서버 LISTEN listen() ESTABLISHED rcv COOKIE-ACK rcv COOKIE-ECHO SHUTDOWN_PENDING close() 호출 SHUTDOWN_SENT DATA ACK 완료 SHUTDOWN_RECEIVED rcv SHUTDOWN SHUTDOWN_ACK_SENT send SHUTDOWN-ACK CLOSED rcv SHUTDOWN-ACK rcv SHUTDOWN-COMPLETE
상태enum 값설명진입 조건
CLOSEDSCTP_STATE_CLOSED초기/종료 상태시작 또는 종료 완료
COOKIE_WAITSCTP_STATE_COOKIE_WAITINIT 전송 후 대기INIT 전송
COOKIE_ECHOEDSCTP_STATE_COOKIE_ECHOEDCOOKIE-ECHO 전송 후 대기INIT-ACK 수신
ESTABLISHEDSCTP_STATE_ESTABLISHED데이터 교환 가능COOKIE-ACK 수신/전송
SHUTDOWN_PENDINGSCTP_STATE_SHUTDOWN_PENDING종료 요청, 미전송 데이터 있음close() 호출
SHUTDOWN_SENTSCTP_STATE_SHUTDOWN_SENTSHUTDOWN 전송 완료미전송 데이터 완료
SHUTDOWN_RECEIVEDSCTP_STATE_SHUTDOWN_RECEIVED상대의 SHUTDOWN 수신SHUTDOWN 청크 수신
SHUTDOWN_ACK_SENTSCTP_STATE_SHUTDOWN_ACK_SENTSHUTDOWN-ACK 전송 완료수신 데이터 처리 완료
/* 커널 내부: Association 상태 전이 — include/net/sctp/constants.h */
enum sctp_state {
    SCTP_STATE_CLOSED            = 0,
    SCTP_STATE_COOKIE_WAIT       = 1,
    SCTP_STATE_COOKIE_ECHOED     = 2,
    SCTP_STATE_ESTABLISHED       = 3,
    SCTP_STATE_SHUTDOWN_PENDING  = 4,
    SCTP_STATE_SHUTDOWN_SENT     = 5,
    SCTP_STATE_SHUTDOWN_RECEIVED = 6,
    SCTP_STATE_SHUTDOWN_ACK_SENT = 7,
};

/* 상태 전이 테이블 — net/sctp/sm_statetable.c */
/* 이벤트(청크 수신/타이머/사용자 명령) + 현재 상태 → 동작 함수 매핑
 * sctp_sm_statetable[event][state] = { .fn = handler, .name = "..." }
 *
 * 예: COOKIE_ECHO 수신 + CLOSED 상태
 *   → sctp_sf_do_5_1D_ce() 호출
 *   → State Cookie 검증 → Association 생성 → ESTABLISHED 전이
 */

혼잡 제어

SCTP의 혼잡 제어는 TCP의 혼잡 제어 알고리즘을 기반으로 하되, 경로별(per-destination) 독립적인 혼잡 상태를 유지합니다. 각 경로(transport)마다 별도의 cwnd(congestion window), ssthresh, RTO를 관리합니다.

SCTP 혼잡 윈도우 진화 (경로별 독립) 시간 (라운드 트립) cwnd ssthresh Slow Start cwnd += MTU per SACK Congestion Avoidance cwnd += MTU per RTT 패킷 손실 ssthresh = cwnd/2 cwnd = 1 MTU 재시작 new ssthresh 각 sctp_transport마다 독립적인 cwnd/ssthresh/RTO 유지 → 한 경로 혼잡이 다른 경로에 영향 없음
파라미터초기값sysctl/소켓 옵션설명
cwnd min(4*MTU, max(2*MTU, 4380)) 자동 계산 혼잡 윈도우 (경로별)
ssthresh a-rwnd (상대 수신 윈도우) 자동 계산 Slow Start 임계값
RTO 3000ms net.sctp.rto_initial 재전송 타임아웃 (경로별)
RTO min 1000ms net.sctp.rto_min RTO 최소값
RTO max 60000ms net.sctp.rto_max RTO 최대값
SRTT 자동 측정 자동 계산 Smoothed Round-Trip Time
RTTVAR 자동 측정 자동 계산 RTT 변이
/* 경로별 혼잡 제어 변수 — include/net/sctp/structs.h */
struct sctp_transport {
    /* ... */
    __u32 cwnd;           /* 혼잡 윈도우 */
    __u32 ssthresh;       /* Slow Start 임계값 */
    __u32 partial_bytes_acked; /* CA 모드 cwnd 증가 추적 */
    __u32 flight_size;    /* 미확인 데이터 크기 */

    /* RTO 계산 */
    unsigned long rto;    /* 재전송 타임아웃 (jiffies) */
    __u32 srtt;            /* Smoothed RTT */
    __u32 rttvar;          /* RTT Variation */
    __u32 rto_pending;     /* RTT 측정 진행 중 */

    /* 장애 카운터 */
    __u16 error_count;     /* 연속 오류 횟수 */
    __u16 pathmaxrxt;      /* 경로 최대 재전송 횟수 */
    /* ... */
};

/* Slow Start 구현 — net/sctp/transport.c */
static void sctp_transport_raise_cwnd(
    struct sctp_transport *transport,
    __u32 sack_ctsn, __u32 bytes_acked)
{
    __u32 cwnd = transport->cwnd;
    __u32 ssthresh = transport->ssthresh;
    __u32 pmtu = transport->pathmtu;

    if (cwnd <= ssthresh) {
        /* Slow Start: cwnd += min(bytes_acked, pmtu) */
        cwnd += min(bytes_acked, pmtu);
    } else {
        /* Congestion Avoidance: cwnd += pmtu per RTT */
        transport->partial_bytes_acked += bytes_acked;
        if (transport->partial_bytes_acked >= cwnd) {
            cwnd += pmtu;
            transport->partial_bytes_acked -= cwnd;
        }
    }
    transport->cwnd = cwnd;
}
Fast Retransmit와 Fast Recovery: SCTP의 Fast Retransmit은 TCP와 유사하게 동일 TSN에 대한 Gap Report가 3번 누적되면 발생합니다. Fast Recovery 모드에서는 ssthresh = max(cwnd/2, 2*MTU)로 설정하고, cwnd = ssthresh로 축소합니다. TCP Reno와 달리 SCTP는 경로별로 독립적인 Fast Recovery를 수행하므로, 한 경로의 손실이 다른 경로의 전송률에 영향을 주지 않습니다.
/* Fast Retransmit 조건 — net/sctp/outqueue.c */
/* sctp_outq_sack() 내부:
 *   각 미확인 청크에 대해 gap_report 카운터를 추적
 *   gap_report가 3 이상이면 Fast Retransmit 대상으로 표시
 */

static void sctp_check_transmitted(
    struct sctp_outq *q,
    struct list_head *transmitted_queue,
    struct sctp_transport *transport,
    union sctp_addr *saddr,
    struct sctp_sackhdr *sack,
    __u32 *highest_new_tsn)
{
    struct sctp_chunk *tchunk;

    list_for_each_entry(tchunk, transmitted_queue, transmitted_list) {
        if (!tchunk->tsn_gap_acked) {
            /* Gap에 포함되지 않은 TSN → 손실 후보 */
            tchunk->tsn_missing_report++;
            if (tchunk->tsn_missing_report >= 3) {
                /* Fast Retransmit: 재전송 큐로 이동 */
                sctp_retransmit_mark(q, tchunk, 0);
            }
        }
    }
}

/* T3-rtx 타이머 만료 시 (RTO 기반 재전송) */
/* → cwnd = 1 MTU로 축소 (Slow Start 재시작)
 * → ssthresh = max(cwnd/2, 4*MTU)
 * → 해당 경로의 미확인 청크 모두 재전송 표시
 * → 보조 경로로 재전송 (primary 장애 가능성)
 */
혼잡 이벤트cwnd 변화ssthresh 변화추가 동작
Slow Start (cwnd ≤ ssthresh) +min(bytes_acked, MTU) per SACK 변경 없음 지수적 증가
Congestion Avoidance +MTU per RTT 변경 없음 선형적 증가
Fast Retransmit (3 Gap Report) = ssthresh = max(cwnd/2, 2*MTU) 즉시 재전송
T3-rtx 타이머 만료 = 1 MTU = max(cwnd/2, 4*MTU) Slow Start 재시작
ECN Echo (ECNE 청크) = ssthresh = max(cwnd/2, 2*MTU) CWR 청크 전송
유휴 후 재개 = max(cwnd, 4*MTU) 변경 없음 Heartbeat 기반 RTT 갱신

SCTP 소켓 API

SCTP는 두 가지 소켓 스타일을 제공합니다: TCP와 유사한 one-to-one (SOCK_STREAM) 스타일과 UDP와 유사한 one-to-many (SOCK_SEQPACKET) 스타일입니다.

특성one-to-one (SOCK_STREAM)one-to-many (SOCK_SEQPACKET)
소켓 타입 SOCK_STREAM SOCK_SEQPACKET
Association 수 1:1 (소켓당 하나) 1:N (소켓에 여러 Association)
listen/accept 사용 (TCP와 동일) listen만 (자동 Association)
API 호환성 TCP 소켓 API 호환 SCTP 전용 API 필요
peeloff 해당 없음 Association을 별도 소켓으로 분리
사용 사례 기존 TCP 코드 마이그레이션 서버 (다중 클라이언트 처리)
/* ===== one-to-one 스타일 (TCP 호환) ===== */
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);

/* bind + listen + accept (TCP와 동일한 패턴) */
struct sockaddr_in addr = {
    .sin_family = AF_INET,
    .sin_port = htons(9999),
    .sin_addr.s_addr = INADDR_ANY,
};
bind(fd, (struct sockaddr *)&addr, sizeof(addr));

/* 스트림 수 설정 (bind 후, listen 전) */
struct sctp_initmsg initmsg = {
    .sinit_num_ostreams  = 10,   /* 출력 스트림 10개 */
    .sinit_max_instreams = 10,   /* 입력 스트림 최대 10개 */
    .sinit_max_attempts  = 4,    /* INIT 재전송 횟수 */
    .sinit_max_init_timeo = 30000, /* INIT 타임아웃 (ms) */
};
setsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg));

listen(fd, 5);
int client_fd = accept(fd, NULL, NULL);

/* 데이터 송수신 — send/recv 또는 sctp_sendmsg/sctp_recvmsg */
send(client_fd, "hello", 5, 0);  /* 기본 스트림 0 */
/* ===== one-to-many 스타일 (SCTP 고유) ===== */
int fd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);

/* bind + listen (accept 없음 — 자동 Association) */
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
listen(fd, 5);

/* 수신: 여러 클라이언트의 메시지를 하나의 소켓으로 */
struct sctp_sndrcvinfo sinfo;
int flags = 0;
int n = sctp_recvmsg(fd, buf, sizeof(buf),
                     (struct sockaddr *)&peer, &peerlen,
                     &sinfo, &flags);
printf("assoc=%u stream=%u\\n",
       sinfo.sinfo_assoc_id, sinfo.sinfo_stream);

/* peeloff: Association을 별도의 one-to-one 소켓으로 분리 */
int peeled_fd = sctp_peeloff(fd, sinfo.sinfo_assoc_id);
/* 이제 peeled_fd로 해당 Association과 1:1 통신 */
send(peeled_fd, "dedicated", 9, 0);
/* RFC 6458: 새로운 sendv/recvv API */
/* sctp_sendv: cmsg 기반 확장 전송 */
struct iovec iov;
struct sctp_sendv_spa spa;

iov.iov_base = data;
iov.iov_len = len;

memset(&spa, 0, sizeof(spa));
spa.sendv_flags = SCTP_SEND_SNDINFO_VALID;
spa.sendv_sndinfo.snd_sid = 3;    /* 스트림 3 */
spa.sendv_sndinfo.snd_ppid = htonl(42);

sctp_sendv(fd, &iov, 1,
           (struct sockaddr *)&dest, 1,
           &spa, sizeof(spa),
           SCTP_SENDV_SPA, 0);

/* sctp_recvv: cmsg 기반 확장 수신 */
struct sctp_rcvinfo rcvinfo;
socklen_t infolen = sizeof(rcvinfo);
unsigned int infotype;
sctp_recvv(fd, &iov, 1,
           (struct sockaddr *)&peer, &peerlen,
           &rcvinfo, &infolen, &infotype, &flags);

PR-SCTP (부분 신뢰성)

PR-SCTP(Partially Reliable SCTP)(RFC 3758)는 SCTP의 신뢰적 전송을 선택적으로 완화하는 확장입니다. 실시간 미디어나 게임 데이터처럼 오래된 데이터보다 최신 데이터가 중요한 경우, 일정 조건에서 재전송을 포기하고 수신 측에 FORWARD-TSN으로 건너뛰기를 알립니다.

PR-SCTP: FORWARD-TSN으로 만료 데이터 건너뛰기 송신자 수신자 DATA TSN=5 (timetolive=500ms) X DATA TSN=6 TSN=5 수명 만료 (500ms 초과) FORWARD-TSN (New Cum TSN=5) 수신자: TSN=5 건너뛰기 → CumTSN=6으로 갱신 SACK: CumTSN=6 재전송 포기 → 대역폭 절약, 수신자 버퍼 해제
PR-SCTP 정책플래그동작사용 사례
Timed Reliability SCTP_PR_SCTP_TTL 지정 시간 후 재전송 포기 실시간 미디어, VoIP
Limited Retransmission SCTP_PR_SCTP_RTX N회 재전송 후 포기 게임 상태 업데이트
Priority Based SCTP_PR_SCTP_PRIO 버퍼 초과 시 낮은 우선순위 폐기 다중 품질 수준 스트리밍
/* PR-SCTP 사용 예시: Timed Reliability */
struct sctp_sndinfo sndinfo;
memset(&sndinfo, 0, sizeof(sndinfo));
sndinfo.snd_sid = 0;
sndinfo.snd_flags = SCTP_UNORDERED;
sndinfo.snd_ppid = htonl(51);  /* WebRTC DCEP */

/* PR-SCTP 정책 설정: 500ms 후 재전송 포기 */
struct sctp_prinfo prinfo;
prinfo.pr_policy = SCTP_PR_SCTP_TTL;
prinfo.pr_value = 500;  /* 밀리초 */

/* sctp_sendv로 PR-SCTP 메시지 전송 */
struct sctp_sendv_spa spa;
spa.sendv_flags = SCTP_SEND_SNDINFO_VALID | SCTP_SEND_PRINFO_VALID;
spa.sendv_sndinfo = sndinfo;
spa.sendv_prinfo = prinfo;
sctp_sendv(fd, &iov, 1, NULL, 0,
           &spa, sizeof(spa), SCTP_SENDV_SPA, 0);

/* Limited Retransmission: 최대 2회 재전송 */
prinfo.pr_policy = SCTP_PR_SCTP_RTX;
prinfo.pr_value = 2;  /* 재전송 횟수 */
PR-SCTP와 메시지 순서: PR-SCTP로 메시지를 폐기하면 해당 스트림의 SSN(Stream Sequence Number)에 간격이 생깁니다. 순서 보장 모드에서는 후속 메시지가 지연될 수 있으므로, PR-SCTP는 보통 SCTP_UNORDERED와 함께 사용합니다.

SCTP 인증 (AUTH 청크)

SCTP AUTH(RFC 4895)는 특정 청크 타입에 대해 HMAC 기반 인증을 제공합니다. Association 설정 시 양쪽이 공유 키와 인증할 청크 타입을 협상하며, AUTH 청크가 인증 대상 청크 앞에 번들링됩니다.

AUTH 구성 요소설명
Shared Key 양쪽이 사전에 설정하는 공유 비밀 키
HMAC Identifier SHA-1(1) 또는 SHA-256(3) 선택
Chunk List 인증할 청크 타입 목록 (ASCONF, ASCONF-ACK 등)
Key ID 여러 키 중 선택을 위한 식별자
/* SCTP 인증 설정 예시 */

/* 1. 공유 키 설정 */
struct sctp_authkey *authkey;
int keylen = sizeof(struct sctp_authkey) + 16;
authkey = malloc(keylen);
authkey->sca_keynumber = 1;       /* Key ID */
authkey->sca_keylength = 16;      /* 키 길이 */
memcpy(authkey->sca_key, "shared_secret_16", 16);
setsockopt(fd, IPPROTO_SCTP, SCTP_AUTH_KEY,
           authkey, keylen);

/* 2. Active Key 설정 */
struct sctp_authkeyid keyid;
keyid.scact_keynumber = 1;
keyid.scact_assoc_id = 0;
setsockopt(fd, IPPROTO_SCTP, SCTP_AUTH_ACTIVE_KEY,
           &keyid, sizeof(keyid));

/* 3. 인증할 청크 타입 설정 */
struct sctp_authchunks *chunks;
int chunklen = sizeof(struct sctp_authchunks) + 2;
chunks = malloc(chunklen);
chunks->gauth_assoc_id = 0;
chunks->gauth_chunks[0] = SCTP_CID_ASCONF;      /* 193 */
chunks->gauth_chunks[1] = SCTP_CID_ASCONF_ACK;  /* 128 */
setsockopt(fd, IPPROTO_SCTP, SCTP_AUTH_CHUNK,
           chunks, chunklen);

UDP 캡슐화 (NAT 통과)

대부분의 NAT 장비와 방화벽은 TCP/UDP만 지원하며 SCTP 패킷을 차단합니다. UDP 캡슐화(RFC 6951)는 SCTP 패킷을 UDP 내부에 넣어 NAT를 통과할 수 있게 합니다. 리눅스 커널은 net.sctp.encap_port sysctl로 이 기능을 제어합니다.

SCTP UDP 캡슐화 패킷 구조 IP 헤더 Proto=17 (UDP) UDP 헤더 SrcPort | DstPort=9899 SCTP 공통 헤더 SrcPort | DstPort | VTag | CRC32c SCTP 청크(들) DATA | SACK | ... 비교: 일반 SCTP (UDP 캡슐화 없음) IP 헤더 Proto=132 (SCTP) SCTP 공통 헤더 SCTP 청크(들) UDP 캡슐화 시 NAT 장비는 외부 UDP 헤더만 처리 → SCTP 내용을 투명하게 통과
# UDP 캡슐화 활성화 (커널 5.11+)
$ sysctl -w net.sctp.encap_port=9899
$ sysctl -w net.sctp.udp_port=9899
/* 소켓 옵션으로 UDP 캡슐화 설정 */
struct sctp_udpencaps encaps;
encaps.sue_assoc_id = 0;
encaps.sue_address = *(struct sockaddr_storage *)&dest;
encaps.sue_port = htons(9899);  /* UDP 캡슐화 포트 */
setsockopt(fd, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT,
           &encaps, sizeof(encaps));
WebRTC와 UDP 캡슐화: WebRTC의 DataChannel은 SCTP over DTLS over UDP 구조를 사용합니다. 이는 커널 SCTP가 아닌 사용자 공간 SCTP 구현(usrsctp)을 사용하지만, 동일한 원리로 NAT 통과 문제를 해결합니다.

커널 소스 구조

리눅스 커널의 SCTP 구현은 net/sctp/ 디렉터리에 위치합니다. 각 파일의 역할을 이해하면 커널 코드를 효율적으로 탐색할 수 있습니다.

소스 파일역할주요 함수/구조체
net/sctp/socket.c 소켓 계층 인터페이스 sctp_sendmsg(), sctp_recvmsg(), setsockopt()
net/sctp/sm_statetable.c 상태 머신 전이 테이블 sctp_sm_statetable[][]
net/sctp/sm_sideeffect.c 상태 머신 부작용 (타이머, 청크 전송) sctp_side_effects(), sctp_cmd_interpreter()
net/sctp/sm_statefuns.c 상태 전이 함수 구현 sctp_sf_do_5_1B_init(), sctp_sf_do_5_1D_ce()
net/sctp/sm_make_chunk.c 청크 생성 sctp_make_init(), sctp_make_sack()
net/sctp/output.c 패킷 출력/번들링 sctp_packet_transmit(), sctp_packet_append_chunk()
net/sctp/input.c 패킷 입력 처리 sctp_rcv(), sctp_v4_rcv()
net/sctp/outqueue.c 전송/재전송 큐 sctp_outq_sack(), sctp_outq_flush()
net/sctp/transport.c 경로(transport) 관리 sctp_transport_raise_cwnd(), sctp_transport_route()
net/sctp/associola.c Association 관리 sctp_association_new(), sctp_assoc_update_retran_path()
net/sctp/endpointola.c 엔드포인트 관리 sctp_endpoint_new(), sctp_endpoint_lookup_assoc()
net/sctp/stream.c 스트림 관리/스케줄러 sctp_stream_init(), sctp_sched_*
net/sctp/auth.c 인증(AUTH) 처리 sctp_auth_init_hmacs(), sctp_auth_send_cid()
net/sctp/sysctl.c sysctl 파라미터 sctp_sysctl_table[]
net/sctp/protocol.c 프로토콜 등록/초기화 sctp_init(), sctp_exit()
include/net/sctp/structs.h 핵심 자료구조 정의 sctp_sock, sctp_association, sctp_transport
include/net/sctp/constants.h 상수/열거형 정의 sctp_state, sctp_cid, sctp_event
include/uapi/linux/sctp.h 사용자 공간 API 정의 소켓 옵션, 청크 헤더, 이벤트 구조체
/* 패킷 수신 진입점 — net/sctp/input.c */
int sctp_rcv(struct sk_buff *skb)
{
    struct sctp_association *asoc;
    struct sctp_endpoint *ep;
    struct sctp_chunk *chunk;
    struct sctphdr *sh;

    /* 1. SCTP 공통 헤더 파싱 */
    sh = sctp_hdr(skb);

    /* 2. CRC32c 체크섬 검증 */
    if (sctp_rcv_checksum(skb) < 0)
        goto discard;

    /* 3. Verification Tag로 Association/Endpoint 검색 */
    asoc = sctp_lookup_association(skb, &ep);

    /* 4. 청크별 파싱 및 상태 머신 전달 */
    chunk = sctp_inq_pop(&asoc->base.inqueue);
    sctp_do_sm(SCTP_EVENT_T_CHUNK, subtype,
               asoc->state, ep, asoc, chunk, GFP_ATOMIC);

    /* 5. 상태 머신이 생성한 부작용(side effect) 실행 */
    sctp_cmd_interpreter(SCTP_EVENT_T_CHUNK, subtype,
                         asoc->state, ep, asoc, chunk,
                         &commands);
    return 0;
discard:
    kfree_skb(skb);
    return 0;
}
/* 상태 머신 실행 — net/sctp/sm_sideeffect.c */
/* 각 상태 전이는 Command 시퀀스를 생성하며,
 * sctp_cmd_interpreter()가 이를 순서대로 실행합니다.
 *
 * 주요 Command 종류:
 *   SCTP_CMD_REPLY      — 응답 청크 전송
 *   SCTP_CMD_SEND_PKT   — 패킷 전송
 *   SCTP_CMD_NEW_STATE  — 상태 전이
 *   SCTP_CMD_TIMER_START — 타이머 시작
 *   SCTP_CMD_TIMER_STOP  — 타이머 정지
 *   SCTP_CMD_NEW_ASOC   — 새 Association 생성
 *   SCTP_CMD_DEL_TCB    — Association 삭제
 *   SCTP_CMD_EVENT_ULP  — 사용자에게 이벤트 알림
 *   SCTP_CMD_PROCESS_SACK — SACK 처리
 */

/* 타이머 종류 — Association/Transport 단위 */
/*
 * T1-init  : INIT 재전송 (Association)
 * T1-cookie: COOKIE-ECHO 재전송 (Association)
 * T2-shutdown: SHUTDOWN 재전송 (Association)
 * T3-rtx   : DATA 재전송 (Transport/경로별)
 * T4-rto   : ASCONF 재전송 (Association)
 * T5-shutdown-guard: 전체 종료 타임아웃 (Association)
 * Heartbeat: 경로 활성 확인 (Transport/경로별)
 * SACK     : 지연 SACK 전송 (Association)
 * Autoclose: 자동 종료 (Association)
 */
커널 디버깅 팁: SCTP 상태 머신의 동작을 추적하려면 ftracesctp_do_sm 함수를 추적하거나, net/sctp/pr_debug() 메시지를 활성화하면 됩니다. 커널 빌드 시 CONFIG_SCTP_DBG_MSG=y를 설정하면 상세 디버그 로그를 볼 수 있습니다.

커널 자료구조

리눅스 커널의 SCTP 구현은 여러 계층의 자료구조로 이루어져 있습니다. 최상위 sctp_sock에서 시작하여 sctp_endpoint, sctp_association, sctp_transport까지 이어지는 계층 구조를 이해하면 커널 코드를 읽기 쉽습니다.

SCTP 커널 자료구조 관계도 sctp_sock 소켓 계층 (include/net/sctp/structs.h) sctp_endpoint 엔드포인트 (바인딩된 주소, 인증 키) sctp_association Association (상태, 스트림, 재전송 큐) 1:N (one-to-many에서 여러 개 가능) sctp_association Association #2 sctp_transport 경로 10.0.0.2 cwnd, ssthresh, rto sctp_transport 경로 10.0.1.2 cwnd, ssthresh, rto sctp_stream in[]/out[] 스트림 배열 SSN, 스케줄러 sctp_outq 전송 큐, 재전송 큐 out_chunk_list, retransmit sctp_sock → sctp_endpoint → sctp_association(들) → sctp_transport(경로) + sctp_stream + sctp_outq
/* 핵심 자료구조 요약 — include/net/sctp/structs.h */

/* sctp_sock: 소켓 계층 */
struct sctp_sock {
    struct inet_sock inet;        /* IPv4/IPv6 소켓 기본 */
    struct sctp_endpoint *ep;      /* 엔드포인트 포인터 */
    struct sctp_bind_bucket *bind_hash;
    __u16 default_stream;          /* 기본 출력 스트림 */
    __u32 default_ppid;            /* 기본 PPID */
    __u16 default_flags;           /* 기본 전송 플래그 */
    __u32 default_context;         /* 기본 컨텍스트 */
    __u32 default_timetolive;      /* 기본 PR-SCTP TTL */
    __u32 default_rcv_context;     /* 기본 수신 컨텍스트 */
    struct sctp_initmsg initmsg;   /* 초기화 파라미터 */
    /* ... */
};

/* sctp_endpoint: 엔드포인트 — 바인딩된 주소와 인증 키 관리 */
struct sctp_endpoint {
    struct sctp_ep_common base;
    struct hlist_node node;      /* 해시 테이블 연결 */
    struct list_head asocs;      /* Association 목록 */
    __u8 secret_key[SCTP_SECRET_SIZE]; /* Cookie HMAC 키 */
    __u8 *auth_hmacs_list;       /* 지원 HMAC 목록 */
    __u8 *auth_chunk_list;       /* 인증 청크 목록 */
    /* ... */
};

/* sctp_transport: 경로 (원격 주소 하나에 대응) */
struct sctp_transport {
    struct list_head transports;   /* Association의 경로 목록 */
    union sctp_addr ipaddr;        /* 원격 주소 */
    __u8 state;                     /* ACTIVE/INACTIVE/PF */
    __u32 cwnd;                     /* 혼잡 윈도우 */
    __u32 ssthresh;                 /* SS 임계값 */
    unsigned long rto;             /* 재전송 타임아웃 */
    __u32 srtt;                     /* Smoothed RTT */
    __u32 pathmtu;                  /* 경로 MTU */
    __u16 error_count;              /* 오류 카운터 */
    __u16 pathmaxrxt;               /* 경로 최대 재전송 */
    struct timer_list T3_rtx_timer; /* 재전송 타이머 */
    struct timer_list hb_timer;     /* Heartbeat 타이머 */
    /* ... */
};
자료구조소스 위치역할주요 필드
sctp_sock include/net/sctp/structs.h 소켓 계층, 사용자 설정 보관 ep, default_stream, initmsg
sctp_endpoint include/net/sctp/structs.h 바인딩 주소, 인증 키, Association 목록 asocs, secret_key, auth_*
sctp_association include/net/sctp/structs.h Association 상태, 스트림, 전송 큐 state, stream, outqueue, peer
sctp_transport include/net/sctp/structs.h 원격 주소별 경로 상태 cwnd, ssthresh, rto, error_count
sctp_stream include/net/sctp/structs.h 입출력 스트림 관리 in[], out[], outcnt, incnt
sctp_outq include/net/sctp/structs.h 전송/재전송 큐 out_chunk_list, retransmit, sacked
sctp_chunk include/net/sctp/structs.h 청크 래퍼 (sk_buff 포함) skb, chunk_hdr, transport, tsn

이벤트 알림

SCTP는 Association 상태 변화, 경로 상태 변화, 오류 발생 등의 이벤트를 애플리케이션에 알림으로 전달할 수 있습니다. SCTP_EVENTS 소켓 옵션으로 수신할 이벤트를 선택하면, recvmsg()의 ancillary data(cmsg)나 메시지 플래그(MSG_NOTIFICATION)를 통해 이벤트를 수신합니다.

이벤트타입 상수설명주요 정보
Association 변화 SCTP_ASSOC_CHANGE Association 상태 전이 state (COMM_UP, COMM_LOST, RESTART, ...)
경로 주소 변화 SCTP_PEER_ADDR_CHANGE 원격 주소 상태 변경 주소, state (ADDR_AVAILABLE, ADDR_UNREACHABLE)
원격 오류 SCTP_REMOTE_ERROR 상대가 ERROR 청크 전송 에러 원인 코드
전송 실패 SCTP_SEND_FAILED 메시지 전달 실패 실패한 메시지 데이터, 오류 코드
종료 이벤트 SCTP_SHUTDOWN_EVENT 상대가 SHUTDOWN 시작 Association ID
적응 레이어 표시 SCTP_ADAPTATION_INDICATION 상대의 Adaptation Layer 정보 Adaptation code
부분 전달 SCTP_PARTIAL_DELIVERY_EVENT 부분 전달 중단 중단 원인
인증 이벤트 SCTP_AUTHENTICATION_EVENT 인증 관련 이벤트 성공/실패, Key ID
송신자 건조 SCTP_SENDER_DRY_EVENT 전송 큐 비어짐 Association ID
/* 이벤트 알림 구독 설정 */
struct sctp_event_subscribe events;
memset(&events, 0, sizeof(events));
events.sctp_data_io_event = 1;          /* DATA 수신 정보 */
events.sctp_association_event = 1;      /* Association 변화 */
events.sctp_address_event = 1;          /* 경로 상태 변화 */
events.sctp_send_failure_event = 1;     /* 전송 실패 */
events.sctp_peer_error_event = 1;       /* 원격 오류 */
events.sctp_shutdown_event = 1;         /* 종료 이벤트 */
events.sctp_sender_dry_event = 1;       /* 전송 큐 비어짐 */
setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events));

/* 이벤트 수신 루프 */
while (1) {
    char buf[4096];
    struct sctp_sndrcvinfo sinfo;
    int flags = 0;

    int n = sctp_recvmsg(fd, buf, sizeof(buf),
                         NULL, 0, &sinfo, &flags);

    if (flags & MSG_NOTIFICATION) {
        /* 알림 메시지 처리 */
        union sctp_notification *snp =
            (union sctp_notification *)buf;

        switch (snp->sn_header.sn_type) {
        case SCTP_ASSOC_CHANGE: {
            struct sctp_assoc_change *sac = &snp->sn_assoc_change;
            printf("Association %s (assoc=%u)\\n",
                   sac->sac_state == SCTP_COMM_UP ? "UP" : "DOWN",
                   sac->sac_assoc_id);
            break;
        }
        case SCTP_PEER_ADDR_CHANGE: {
            struct sctp_paddr_change *spc = &snp->sn_paddr_change;
            printf("Path state: %d\\n", spc->spc_state);
            break;
        }
        case SCTP_SHUTDOWN_EVENT:
            printf("Peer initiated shutdown\\n");
            break;
        }
    } else {
        /* 일반 데이터 처리 */
        printf("Data on stream %u: %.*s\\n",
               sinfo.sinfo_stream, n, buf);
    }
}

SCTP vs TCP vs UDP 상세 비교

전송 프로토콜 선택은 애플리케이션의 요구사항에 따라 달라집니다. 다음은 세 프로토콜의 상세한 비교입니다.

TCP vs SCTP 아키텍처 비교 TCP 단일 바이트 스트림 단일 경로 (src IP, dst IP) 3-way handshake (SYN Flood 취약) 4-way 종료 (FIN + TIME_WAIT) Head-of-Line Blocking 발생 SCTP 다중 독립 스트림 (메시지 경계) 다중 경로 + 자동 Failover 4-way handshake (DoS 방어 내장) 3-way 종료 (TIME_WAIT 없음) 스트림 독립으로 HoL 방지
항목TCPUDPSCTP
IP 프로토콜 번호617132
데이터 단위바이트 스트림데이터그램메시지 (청크)
연결 설정3-way handshake없음4-way handshake
연결 종료4-way (FIN/ACK)없음3-way (SHUTDOWN)
멀티호밍불가능불가능지원 (자동 failover)
멀티스트리밍불가능불가능지원 (독립 스트림)
HoL Blocking발생해당 없음스트림 독립으로 방지
메시지 경계보존하지 않음보존보존
혼잡 제어단일 경로없음경로별 독립
부분 신뢰성불가능해당 없음PR-SCTP 지원
NAT 통과자연 지원자연 지원UDP 캡슐화 필요
커널 기본 지원항상 내장항상 내장모듈 로드 필요
대표 사용 사례HTTP, SSH, DBDNS, 게임, VoIP텔레콤, WebRTC
SCTP 도입 시 주의사항: SCTP는 기술적으로 우수하지만 실제 배포에서는 몇 가지 장벽이 있습니다: (1) 대부분의 NAT/방화벽이 SCTP를 차단 → UDP 캡슐화 필요, (2) 커널 모듈 별도 로드 필요, (3) 미들박스 호환성 문제, (4) 디버깅 도구(Wireshark는 지원)가 TCP만큼 풍부하지 않음. 새 프로젝트에서 SCTP를 사용하려면 네트워크 경로의 SCTP 지원 여부를 먼저 확인하세요.
사용 사례권장 프로토콜이유
웹 서비스 (HTTP/HTTPS) TCP (QUIC) 범용 지원, NAT 통과 용이
DNS 조회 UDP 단일 요청/응답, 오버헤드 최소
텔레콤 시그널링 (Diameter, SS7) SCTP 멀티호밍 failover, 메시지 경계 필수
WebRTC DataChannel SCTP (over DTLS/UDP) 멀티스트리밍, 부분 신뢰성
실시간 게임 UDP / SCTP SCTP PR-SCTP로 오래된 데이터 폐기
고가용성 클러스터 SCTP 멀티호밍으로 경로 이중화
데이터베이스 복제 TCP 완전한 신뢰성, 순서 보장 필요
VoIP 시그널링 (SIP) TCP / SCTP SCTP의 메시지 경계가 SIP에 적합
/* 완전한 one-to-one SCTP 서버 예제 */
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/sctp.h>

int main(void)
{
    int listen_fd, conn_fd;
    struct sockaddr_in servaddr;
    struct sctp_initmsg initmsg;
    struct sctp_event_subscribe events;
    char buf[1024];

    /* 소켓 생성: one-to-one 스타일 */
    listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);

    /* 주소 바인딩 */
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);
    bind(listen_fd, (struct sockaddr *)&servaddr,
         sizeof(servaddr));

    /* 스트림 수 설정 */
    memset(&initmsg, 0, sizeof(initmsg));
    initmsg.sinit_num_ostreams = 5;
    initmsg.sinit_max_instreams = 5;
    setsockopt(listen_fd, IPPROTO_SCTP, SCTP_INITMSG,
               &initmsg, sizeof(initmsg));

    /* 이벤트 구독 */
    memset(&events, 0, sizeof(events));
    events.sctp_data_io_event = 1;
    setsockopt(listen_fd, IPPROTO_SCTP, SCTP_EVENTS,
               &events, sizeof(events));

    /* 리슨 시작 */
    listen(listen_fd, 5);
    printf("SCTP server listening on port 9999\\n");

    /* 클라이언트 수락 */
    conn_fd = accept(listen_fd, NULL, NULL);

    /* 데이터 수신 루프 */
    struct sctp_sndrcvinfo sinfo;
    int flags = 0;
    int n;
    while ((n = sctp_recvmsg(conn_fd, buf, sizeof(buf),
                              NULL, 0, &sinfo, &flags)) > 0) {
        printf("stream=%u: %.*s\\n", sinfo.sinfo_stream, n, buf);

        /* 에코: 같은 스트림으로 응답 */
        sctp_sendmsg(conn_fd, buf, n, NULL, 0,
                     sinfo.sinfo_ppid, 0,
                     sinfo.sinfo_stream, 0, 0);
    }

    close(conn_fd);
    close(listen_fd);
    return 0;
}
# SCTP 서버 예제 컴파일 및 실행
$ gcc -o sctp_server sctp_server.c -lsctp
$ ./sctp_server

# 클라이언트 테스트 (lksctp-tools)
$ sctp_test -H 127.0.0.1 -h 127.0.0.1 -P 9999 -p 9999 -s

운영 튜닝 포인트

SCTP의 동작은 sysctl 파라미터와 소켓 옵션으로 세밀하게 조정할 수 있습니다. 다음은 주요 sysctl 파라미터와 권장 설정입니다.

sysctl 파라미터기본값설명권장 (고가용성)
net.sctp.rto_initial 3000ms 초기 RTO 1000ms
net.sctp.rto_min 1000ms RTO 최소값 200ms
net.sctp.rto_max 60000ms RTO 최대값 10000ms
net.sctp.hb_interval 30000ms Heartbeat 간격 10000ms
net.sctp.path_max_retrans 5 경로 최대 재전송 횟수 3
net.sctp.max_retrans_association 10 Association 최대 재전송 10
net.sctp.max_retrans_init 8 INIT 최대 재전송 4
net.sctp.valid_cookie_life 60000ms State Cookie 유효 기간 60000ms
net.sctp.sack_timeout 200ms SACK 지연 전송 타임아웃 50ms (저지연)
net.sctp.addip_enable 0 ASCONF (동적 주소 변경) 1 (필요시)
net.sctp.prsctp_enable 1 PR-SCTP 지원 1
net.sctp.auth_enable 0 AUTH 청크 지원 1 (보안 필요시)
net.sctp.encap_port 0 UDP 캡슐화 포트 9899 (NAT 통과시)
# 고가용성 환경 튜닝 예시
# Heartbeat 간격 단축 → 빠른 장애 감지
$ sysctl -w net.sctp.hb_interval=10000

# RTO 최소/최대 조정 → 빠른 재전송
$ sysctl -w net.sctp.rto_min=200
$ sysctl -w net.sctp.rto_max=10000
$ sysctl -w net.sctp.rto_initial=1000

# 경로 재전송 한계 축소 → 빠른 failover
$ sysctl -w net.sctp.path_max_retrans=3

# SACK 지연 축소 → 빠른 확인응답
$ sysctl -w net.sctp.sack_timeout=50

# 동적 주소 변경 허용
$ sysctl -w net.sctp.addip_enable=1

# PR-SCTP 확인
$ sysctl net.sctp.prsctp_enable

# 영구 적용: /etc/sysctl.d/sctp.conf
$ cat > /etc/sysctl.d/sctp.conf <<EOF
net.sctp.rto_min = 200
net.sctp.rto_max = 10000
net.sctp.rto_initial = 1000
net.sctp.hb_interval = 10000
net.sctp.path_max_retrans = 3
net.sctp.sack_timeout = 50
EOF
$ sysctl -p /etc/sysctl.d/sctp.conf

트러블슈팅

연결이 성립하지 않을 때: 방화벽에서 IP proto 132(SCTP) 차단 여부와 NAT 장비의 SCTP 처리 지원 여부를 먼저 확인하세요. 중간 경로의 라우터나 미들박스가 SCTP를 차단하는 경우도 많습니다.
# 1. 커널 모듈 확인
$ lsmod | grep sctp
sctp                  413696  0

# 모듈이 없으면 로드
$ modprobe sctp

# 2. SCTP 소켓 상태 확인
$ ss -n -A sctp
LISTEN  0   5   *:9999   *:*
ESTAB   0   0   10.0.0.1:9999  10.0.0.2:9999

# 3. sysctl 현재 값 확인
$ sysctl -a | grep sctp

# 4. 방화벽 규칙 확인 (SCTP 허용)
$ iptables -L -n | grep sctp
$ nft list ruleset | grep sctp

# 5. tcpdump로 SCTP 패킷 캡처
$ tcpdump -i eth0 -n sctp -vv

# 6. sctp_test 도구 (lksctp-tools)
# 서버
$ sctp_test -H 10.0.0.2 -P 9999 -l
# 클라이언트
$ sctp_test -H 10.0.0.1 -h 10.0.0.2 -P 9999 -p 9999 -s

# 7. /proc 인터페이스
$ cat /proc/net/sctp/snmp
$ cat /proc/net/sctp/eps     # 엔드포인트 목록
$ cat /proc/net/sctp/assocs  # Association 목록
$ cat /proc/net/sctp/remaddr # 원격 주소 목록

# 8. Wireshark SCTP 분석
# Wireshark에서 "sctp" 필터로 패킷 분석 가능
# Statistics → SCTP → Show All Associations 메뉴 활용
증상원인해결 방법
INIT 후 응답 없음 방화벽이 SCTP(proto 132) 차단 iptables -A INPUT -p sctp -j ACCEPT
INIT-ACK 수신 후 COOKIE-ECHO 실패 NAT가 SCTP를 지원하지 않음 UDP 캡슐화 사용 (net.sctp.encap_port)
빈번한 경로 전환 Heartbeat 간격이 너무 짧음 hb_interval 증가
Association 비정상 종료 (ABORT) State Cookie 만료 valid_cookie_life 증가
높은 재전송률 RTO가 네트워크 지연에 비해 짧음 rto_min 조정
모듈 로드 실패 CONFIG_IP_SCTP가 비활성 커널 재빌드 또는 배포판 모듈 설치
sctp_sendmsg 실패 (ENOMEM) 수신 윈도우(a-rwnd) 소진 수신 측 처리 속도 개선, 버퍼 증가
디버깅 팁: SCTP 디버깅 시 /proc/net/sctp/assocs에서 각 Association의 상태, 스트림 수, 원격 주소 수를 확인할 수 있습니다. Wireshark의 SCTP 분석 기능은 Association별 TSN 그래프, 재전송 통계, 경로별 RTT를 시각화해줍니다.

SCTP 통계와 모니터링

커널은 /proc/net/sctp/snmp에서 SCTP MIB(RFC 3873) 기반 통계를 제공합니다. 이 통계를 모니터링하면 SCTP 스택의 건강 상태를 파악하고 성능 문제를 진단할 수 있습니다.

MIB 카운터설명비정상 증가 시 의미
SctpCurrEstab 현재 ESTABLISHED Association 수 예상보다 높으면 연결 누수 의심
SctpActiveEstabs 클라이언트 측에서 성공한 Association 수 정상 활동 지표
SctpPassiveEstabs 서버 측에서 수락한 Association 수 정상 활동 지표
SctpAborteds ABORT로 종료된 Association 수 높으면 비정상 종료 빈발
SctpShutdowns 정상 종료(SHUTDOWN)된 Association 수 정상 활동 지표
SctpOutOfBlues 미식별 Association의 패킷 수 높으면 스캔 공격 또는 설정 오류
SctpChecksumErrors 체크섬 오류 패킷 수 높으면 네트워크 장비 문제
SctpT1InitExpireds T1-init 타이머 만료 횟수 높으면 서버 도달 불가
SctpT3RtxExpireds T3-rtx 타이머 만료 횟수 높으면 네트워크 손실 심함
SctpInSCTPPacks 수신한 SCTP 패킷 총 수 처리량 지표
SctpOutSCTPPacks 전송한 SCTP 패킷 총 수 처리량 지표
SctpFragUsrMsgs 단편화된 사용자 메시지 수 높으면 메시지 크기 조정 권장
SctpReasmUsrMsgs 재조립된 사용자 메시지 수 단편화 대응 지표
SctpInCtrlChunks 수신한 제어 청크 수 프로토콜 오버헤드 지표
SctpOutCtrlChunks 전송한 제어 청크 수 프로토콜 오버헤드 지표
# SCTP SNMP 통계 확인
$ cat /proc/net/sctp/snmp
SctpCurrEstab                   	3
SctpActiveEstabs                	15
SctpPassiveEstabs               	12
SctpAborteds                    	2
SctpShutdowns                   	10
SctpOutOfBlues                  	0
SctpChecksumErrors              	0
SctpOutCtrlChunks               	1247
SctpOutOrderChunks              	5832
SctpOutUnorderChunks            	0
SctpInCtrlChunks                	1251
SctpInOrderChunks               	5828
SctpInUnorderChunks             	0
SctpFragUsrMsgs                 	47
SctpReasmUsrMsgs                	47
SctpInSCTPPacks                 	7079
SctpOutSCTPPacks                	7079
SctpT1InitExpireds              	0
SctpT3RtxExpireds               	3

# Association 목록 확인
$ cat /proc/net/sctp/assocs
 ASSOC     SOCK   STY SST ST HBKT ASSOC-ID TX_QUEUE RX_QUEUE UID INODE LPORT RPORT
 ffff...   ffff...  2   1  3    0        1        0        0 1000  12345  9999  9999

# 엔드포인트 목록 확인
$ cat /proc/net/sctp/eps
 ENDPT     SOCK   STY SST HBKT LPORT   UID INODE LADDRS
 ffff...   ffff...  2   10    0  9999 1000 12345 10.0.0.1 10.0.1.1

# 원격 주소 목록 확인
$ cat /proc/net/sctp/remaddr
 ADDR ASSOC_ID HB_ACT RTO MAX_PATH_RTX REM_ADDR_RTX  START  STATE
 10.0.0.2    1      1 1000          5            0      1  ACTIVE
 10.0.1.2    1      1 3000          5            0      1  ACTIVE
# nstat로 SCTP 통계 변화 모니터링 (실시간)
$ nstat -z | grep Sctp
SctpActiveEstabs                2                  0.0
SctpPassiveEstabs               1                  0.0
SctpOutSCTPPacks                147                0.0
SctpInSCTPPacks                 152                0.0

# ss 명령어로 SCTP Association 상세 정보
$ ss -n -A sctp -i
State  Recv-Q Send-Q  Local Address:Port  Peer Address:Port
ESTAB  0      0      10.0.0.1:9999    10.0.0.2:9999
         skmem:(r0,rb212992,t0,tb212992,f0,w0,o0,bl0,d0)

# eBPF를 사용한 SCTP 추적 (bpftrace)
$ bpftrace -e 'kprobe:sctp_rcv { @pkts = count(); }'
$ bpftrace -e 'kprobe:sctp_do_sm { @events[arg1] = count(); }'
성능 모니터링 주의: /proc/net/sctp/snmp의 카운터는 전역 통계입니다. 특정 Association의 통계가 필요하면 SCTP_GET_ASSOC_STATS 소켓 옵션을 사용하세요. 이를 통해 Association별 전송/수신 바이트, 재전송 횟수, 최대 RTO 등을 확인할 수 있습니다.

SCTP와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.

참고자료