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 스트림 스케줄러

RFC 8260은 SCTP 스트림 스케줄링과 사용자 메시지 인터리빙(User Message Interleaving)을 정의합니다. 기본적으로 SCTP는 FIFO(선입선출) 순서로 청크를 전송하지만, 여러 스트림에 동시에 큰 메시지가 있으면 특정 스트림이 다른 스트림을 차단할 수 있습니다. 스트림 스케줄러는 이 문제를 해결합니다.

스트림 스케줄러: FIFO vs Round-Robin vs Priority FIFO (기본) S0-1 S0-2 S0-3 S1-1 S2-1 S0 대량 전송 → S1, S2 차단 Head-of-Line Blocking 발생 Round-Robin S0 S1 S2 S0 균등 분배 → 공정성 보장 모든 스트림에 동일 기회 Priority S0 (High) S1 (Med) S2 (Lo) 높은 우선순위 스트림 먼저 제어 채널 우선 전송에 적합 I-DATA 청크: 사용자 메시지 인터리빙 (RFC 8260) 기존 DATA 청크: 하나의 메시지가 여러 청크로 분할되면, 모든 조각이 전송될 때까지 다른 스트림 차단 S0-F1 S0-F2 S0-F3 S1-F1 ← S1은 S0 완료 후에야 전송 I-DATA 청크: MID(Message ID)로 조각을 구분 → 서로 다른 메시지의 조각을 자유롭게 인터리빙 S0-F1 S1-F1 S0-F2 S1-F2 S0-F3 ← 두 스트림이 교대로 전송
스케줄러커널 상수동작사용 사례
FIFO (기본) SCTP_SS_FCFS enqueue 순서대로 전송 단일 스트림 또는 메시지가 작을 때
Round-Robin SCTP_SS_RR 활성 스트림 순환 균등 대역폭 분배
Round-Robin per Packet SCTP_SS_RR_PKT 패킷 단위 라운드 로빈 더 세밀한 공정성
Priority SCTP_SS_PRIO 스트림 우선순위에 따라 전송 제어 채널 우선 전송
Fair Capacity SCTP_SS_FC 공정 용량 기반 (WFQ 유사) 가중치 기반 대역폭 분배
Weighted Fair Queue SCTP_SS_WFQ 가중 공정 큐잉 QoS 차등 스트림
/* 스트림 스케줄러 설정 */
struct sctp_assoc_value av;
av.assoc_id = 0;
av.assoc_value = SCTP_SS_RR;  /* Round-Robin */
setsockopt(fd, IPPROTO_SCTP, SCTP_STREAM_SCHEDULER,
           &av, sizeof(av));

/* 스트림 우선순위 설정 (Priority 스케줄러용) */
struct sctp_stream_value sv;
sv.assoc_id = 0;
sv.stream_id = 0;        /* 스트림 0 */
sv.stream_value = 0;     /* 최고 우선순위 (0이 가장 높음) */
setsockopt(fd, IPPROTO_SCTP, SCTP_STREAM_SCHEDULER_VALUE,
           &sv, sizeof(sv));

sv.stream_id = 1;
sv.stream_value = 10;    /* 낮은 우선순위 */
setsockopt(fd, IPPROTO_SCTP, SCTP_STREAM_SCHEDULER_VALUE,
           &sv, sizeof(sv));

/* I-DATA 인터리빙 활성화 (RFC 8260) */
int enable = 1;
setsockopt(fd, IPPROTO_SCTP, SCTP_INTERLEAVING_SUPPORTED,
           &enable, sizeof(enable));
setsockopt(fd, IPPROTO_SCTP, SCTP_FRAGMENT_INTERLEAVE,
           &enable, sizeof(enable));
/* 커널 스트림 스케줄러 인터페이스 — net/sctp/stream_sched.c */
struct sctp_sched_ops {
    /* 스트림에 청크 enqueue */
    int  (*enqueue)(struct sctp_outq *q,
                    struct sctp_datamsg *msg);
    /* 다음 전송할 청크 선택 */
    struct sctp_chunk *(*dequeue)(
                    struct sctp_outq *q);
    /* 스트림에서 청크 제거 */
    void (*dequeue_done)(struct sctp_outq *q,
                         struct sctp_chunk *ch);
    /* 스케줄러 초기화/해제 */
    int  (*init)(struct sctp_stream *stream);
    void (*free)(struct sctp_stream *stream);
    /* 스트림 초기화/해제 */
    int  (*init_sid)(struct sctp_stream *stream,
                     __u16 sid, gfp_t gfp);
    void (*free_sid)(struct sctp_stream *stream,
                     __u16 sid);
};

/* I-DATA 청크 구조 — DATA 청크와의 차이 */
/*
 * DATA 청크:  Type(0x00) | Flags | Length | TSN | SID | SSN | PPID | Data
 * I-DATA 청크: Type(0x40) | Flags | Length | TSN | SID | MID | PPID/FSID | Data
 *
 * 차이점:
 * - SSN(Stream Sequence Number, 16비트) → MID(Message ID, 32비트)
 * - 첫 조각: PPID 포함, 후속 조각: FSN(Fragment Sequence Number)
 * - MID가 32비트이므로 순서 번호 고갈 문제 해결
 * - 서로 다른 메시지의 조각을 자유롭게 인터리빙 가능
 */
인터리빙이 중요한 이유: WebRTC DataChannel처럼 하나의 Association에서 제어 메시지와 대용량 파일을 동시에 보내는 경우, I-DATA 인터리빙이 없으면 큰 파일 전송이 작은 제어 메시지를 차단합니다. RFC 8260의 인터리빙을 활성화하면 큰 메시지의 조각 사이에 작은 메시지를 끼워 넣을 수 있어 지연 시간이 크게 줄어듭니다.

Dynamic Address Reconfiguration (ASCONF)

ASCONF(Address Configuration Change)(RFC 5061)는 Association이 활성 상태인 동안 IP 주소를 동적으로 추가하거나 삭제할 수 있게 해주는 확장입니다. 모바일 환경에서 네트워크 인터페이스가 변경되거나, 서버 마이그레이션 시 중단 없이 주소를 전환하는 데 핵심적입니다.

ASCONF: 동적 주소 추가/삭제 과정 호스트 A 10.0.0.1 (Primary) 호스트 B 10.0.0.2 새 NIC 활성화: 10.0.1.1 ASCONF: Add IP 10.0.1.1 파라미터: SCTP_PARAM_ADD_IP ASCONF-ACK: Success 로컬 주소: 10.0.0.1, 10.0.1.1 멀티호밍 경로 2개 활성 10.0.0.1 NIC 분리 예정 ASCONF: Set Primary 10.0.1.1 ASCONF-ACK: Success ASCONF: Delete IP 10.0.0.1 결과: 무중단 주소 전환 완료
ASCONF 파라미터코드설명조건
Add IP Address 0xC001 Association에 새 IP 주소 추가 양쪽 모두 ASCONF 지원 필수
Delete IP Address 0xC002 Association에서 IP 주소 제거 마지막 주소는 삭제 불가
Set Primary Address 0xC004 Primary 경로 변경 요청 상대가 해당 주소를 갖고 있어야
Success Indication 0xC005 ASCONF-ACK 성공 응답
Error Cause 0xC006 ASCONF-ACK 오류 응답 사유 코드 포함
/* ASCONF 활성화 및 동적 주소 변경 */

/* 1. ASCONF 기능 활성화 (커널 sysctl) */
/* sysctl -w net.sctp.addip_enable=1 */
/* sysctl -w net.sctp.addip_noauth_enable=1  (테스트용, AUTH 없이 허용) */

/* 2. sctp_bindx: 동적으로 주소 추가/삭제 */
struct sockaddr_in new_addr;
new_addr.sin_family = AF_INET;
new_addr.sin_port = htons(9999);
inet_pton(AF_INET, "10.0.1.1", &new_addr.sin_addr);

/* Association 활성 중 주소 추가 → ASCONF 자동 전송 */
sctp_bindx(fd, (struct sockaddr *)&new_addr, 1,
           SCTP_BINDX_ADD_ADDR);

/* Association 활성 중 주소 제거 → ASCONF 자동 전송 */
struct sockaddr_in old_addr;
old_addr.sin_family = AF_INET;
old_addr.sin_port = htons(9999);
inet_pton(AF_INET, "10.0.0.1", &old_addr.sin_addr);
sctp_bindx(fd, (struct sockaddr *)&old_addr, 1,
           SCTP_BINDX_REM_ADDR);

/* 3. Primary 경로 변경 */
struct sctp_setpeerprim prim;
prim.sspp_assoc_id = 0;
prim.sspp_addr = *(struct sockaddr_storage *)&new_addr;
setsockopt(fd, IPPROTO_SCTP, SCTP_SET_PEER_PRIMARY_ADDR,
           &prim, sizeof(prim));
/* 커널 ASCONF 처리 — net/sctp/sm_make_chunk.c */
/*
 * sctp_process_asconf(): ASCONF 청크 수신 처리
 *   1. 각 ASCONF 파라미터 순회
 *   2. Add IP → sctp_assoc_add_peer() 호출
 *   3. Delete IP → sctp_assoc_del_peer() 호출
 *   4. Set Primary → 상대의 primary 경로 변경 요청
 *   5. 결과를 ASCONF-ACK로 응답
 *
 * 보안 주의: ASCONF는 AUTH 청크로 인증해야 안전
 * (net.sctp.addip_noauth_enable=0 권장)
 * 인증 없이 ASCONF를 허용하면 공격자가
 * Association을 하이재킹할 수 있음
 */

/* T4-rto 타이머: ASCONF 재전송 */
/*
 * ASCONF 전송 후 ASCONF-ACK를 받지 못하면
 * T4-rto 타이머 만료 → ASCONF 재전송
 * 최대 재전송 횟수: association_max_retrans
 * 모두 실패 시 Association 중단(ABORT)
 */
ASCONF 보안 필수: net.sctp.addip_noauth_enable=1은 테스트 환경에서만 사용하세요. 운영 환경에서는 반드시 AUTH 청크(RFC 4895)와 함께 사용하여 ASCONF 인증을 활성화해야 합니다. 인증 없는 ASCONF는 공격자가 가짜 주소를 삽입하여 트래픽을 가로챌 수 있습니다.

Heartbeat와 PF(Potentially Failed) 상태

SCTP는 Heartbeat 메커니즘으로 각 경로의 활성 상태를 확인합니다. 비활성 경로(데이터 전송이 없는 경로)에 주기적으로 HEARTBEAT 청크를 보내고, HEARTBEAT-ACK를 받으면 경로가 살아 있음을 확인합니다. 리눅스 커널은 RFC 7829의 PF(Potentially Failed) 상태를 추가로 구현하여, 완전 실패 선언 전에 중간 상태를 두어 더 빠른 failover를 가능하게 합니다.

SCTP 경로 상태 전이: ACTIVE → PF → INACTIVE ACTIVE 정상 전송 가능 PF (Potentially Failed) 전송 보류, HB 계속 INACTIVE 사용 불가 error_count ≥ pf_retrans error_count ≥ pathmaxrxt HB-ACK 수신 → 복구 HB-ACK 수신 → 경로 복원 Heartbeat 타이밍 t HB HB-ACK HB 타임아웃 HB(재전송) 타임아웃 INACTIVE ACTIVE PF INACTIVE hb_interval + RTO jitter
경로 상태커널 상수조건동작
ACTIVE SCTP_ACTIVE error_count < pf_retrans 정상 데이터 전송 가능
PF SCTP_PF pf_retrans ≤ error_count < pathmaxrxt 데이터 전송 보류, Heartbeat만 전송
INACTIVE SCTP_INACTIVE error_count ≥ pathmaxrxt 사용 불가, Heartbeat로 복구 시도
UNCONFIRMED SCTP_UNCONFIRMED ASCONF로 추가된 미확인 경로 Heartbeat 확인 후 ACTIVE 전환
/* PF(Potentially Failed) 상태 설정 — 소켓 옵션 */

/* pf_retrans: PF 상태로 전환하는 오류 임계값 */
/* error_count가 이 값에 도달하면 경로를 PF로 표시 */
struct sctp_assoc_value pf;
pf.assoc_id = 0;
pf.assoc_value = 1;  /* 1회 실패 시 PF 전환 (빠른 감지) */
setsockopt(fd, IPPROTO_SCTP, SCTP_PEER_ADDR_THLDS,
           &pf, sizeof(pf));

/* 또는: 경로별 PF 임계값 설정 */
struct sctp_paddrthlds thlds;
thlds.spt_assoc_id = 0;
thlds.spt_address = *(struct sockaddr_storage *)&peer_addr;
thlds.spt_pathmaxrxt = 5;   /* INACTIVE 임계값 */
thlds.spt_pathpfthld = 1;    /* PF 임계값 */
setsockopt(fd, IPPROTO_SCTP, SCTP_PEER_ADDR_THLDS_V2,
           &thlds, sizeof(thlds));
/* Heartbeat 커널 구현 — net/sctp/transport.c */

/* Heartbeat 타이머 콜백 */
static void sctp_transport_timeout(struct timer_list *t)
{
    struct sctp_transport *transport =
        from_timer(transport, t, hb_timer);
    /*
     * 1. HB 타이머 만료 → HEARTBEAT 청크 생성
     * 2. 상태 머신에 SCTP_EVENT_TIMEOUT_HB 이벤트 전달
     * 3. sctp_sf_sendbeat_8_3() → HEARTBEAT 청크 전송
     * 4. 현재 시각을 HB에 포함 (RTT 측정용)
     */
}

/* Heartbeat 간격 계산 */
/*
 * HB 간격 = hb_interval + RTO + 랜덤 지터
 * 랜덤 지터: 0 ~ RTO 사이의 값 (동시 HB 방지)
 *
 * 경로에 활성 데이터 전송이 있으면 HB 생략
 * (데이터 ACK가 경로 확인 역할을 대신함)
 */

/* PF 상태 전환 — net/sctp/transport.c */
static void sctp_transport_update_state(
    struct sctp_transport *transport)
{
    /* error_count 증가 시 호출 */
    if (transport->error_count >= transport->pathmaxrxt) {
        /* INACTIVE: 완전 실패 */
        transport->state = SCTP_INACTIVE;
        /* 이 경로가 primary면 다른 ACTIVE 경로로 전환 */
    } else if (transport->error_count >= transport->pf_retrans) {
        /* PF: 잠재적 실패 → 새 데이터는 다른 경로로 */
        transport->state = SCTP_PF;
        /* PF 경로에는 HB만 계속 전송 */
    }
}

/* HB-ACK 수신 시 경로 복구 */
/*
 * sctp_sf_backbeat_8_3():
 *   1. error_count = 0 으로 리셋
 *   2. 상태를 ACTIVE로 복원
 *   3. HB에 포함된 시각으로 RTT 계산
 *   4. RTO 갱신: RTO = SRTT + 4*RTTVAR
 */
PF의 장점: PF 상태 없이는 경로가 ACTIVE에서 바로 INACTIVE로 전환되어, pathmaxrxt만큼의 타임아웃을 모두 기다려야 합니다. PF 상태를 사용하면 1~2회 실패만으로 즉시 대안 경로를 사용하면서, 원래 경로의 복구를 Heartbeat로 계속 확인합니다. 텔레콤 환경에서 failover 시간을 수십 초에서 수백 밀리초로 단축할 수 있습니다.

SCTP over IPv6

SCTP는 IPv6를 완벽히 지원하며, IPv4/IPv6 듀얼 스택 환경에서 하나의 Association에 두 프로토콜 패밀리의 주소를 동시에 사용할 수 있습니다. 이는 TCP에서는 불가능한 SCTP 고유의 멀티호밍 기능입니다.

기능IPv4 전용IPv6 전용듀얼 스택
소켓 생성 AF_INET AF_INET6 AF_INET6 + IPV6_V6ONLY=0
멀티호밍 IPv4 주소끼리만 IPv6 주소끼리만 IPv4 + IPv6 주소 혼합 가능
INIT 주소 교환 IPv4 Address 파라미터 IPv6 Address 파라미터 두 종류 모두 포함
경로 failover IPv4 ↔ IPv4 IPv6 ↔ IPv6 IPv4 ↔ IPv6 교차 가능
커널 구현 net/sctp/protocol.c net/sctp/ipv6.c 두 파일 모두 사용
/* SCTP 듀얼 스택 서버: IPv4 + IPv6 동시 수신 */
int fd = socket(AF_INET6, SOCK_SEQPACKET, IPPROTO_SCTP);

/* 듀얼 스택 활성화 (IPv4-mapped IPv6) */
int v6only = 0;
setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only));

/* IPv6 + IPv4 주소 모두 바인딩 */
struct sockaddr_in6 addr6 = {
    .sin6_family = AF_INET6,
    .sin6_port = htons(9999),
    .sin6_addr = in6addr_any,  /* :: */
};
bind(fd, (struct sockaddr *)&addr6, sizeof(addr6));

/* 멀티호밍: 추가 IPv4 주소 바인딩 */
struct sockaddr_in addr4 = {
    .sin_family = AF_INET,
    .sin_port = htons(9999),
};
inet_pton(AF_INET, "10.0.0.1", &addr4.sin_addr);
sctp_bindx(fd, (struct sockaddr *)&addr4, 1,
           SCTP_BINDX_ADD_ADDR);

/* 추가 IPv6 주소 바인딩 */
struct sockaddr_in6 addr6_2 = {
    .sin6_family = AF_INET6,
    .sin6_port = htons(9999),
};
inet_pton(AF_INET6, "2001:db8::1", &addr6_2.sin6_addr);
sctp_bindx(fd, (struct sockaddr *)&addr6_2, 1,
           SCTP_BINDX_ADD_ADDR);

listen(fd, 5);
/* 이제 IPv4와 IPv6 클라이언트 모두 연결 가능 */
/* failover 시 IPv4 경로 ↔ IPv6 경로 교차 전환 가능 */
/* 커널 IPv6 SCTP 구현 — net/sctp/ipv6.c */

/* IPv6 전용 주소 비교 함수 */
static int sctp_v6_cmp_addr(
    const union sctp_addr *addr1,
    const union sctp_addr *addr2)
{
    /* IPv6 주소 비교: 스코프 ID도 함께 비교 */
    /* link-local 주소의 경우 인터페이스 인덱스가 다르면 다른 주소 */
}

/* IPv6 scope 기반 주소 선택 */
/*
 * SCTP는 INIT에 로컬 주소를 포함할 때 scope를 고려:
 * - link-local (fe80::) : 같은 링크에서만 사용
 * - site-local (fec0::, deprecated) : 사이트 내부용
 * - global (2000:: ~ 3fff::) : 전역 도달 가능
 *
 * 원격 주소가 global이면 link-local 주소는 INIT에 포함하지 않음
 * (도달 불가능한 주소를 광고하면 failover 실패)
 */

/* 듀얼 스택 프로토콜 등록 */
static struct sctp_af sctp_af_inet6 = {
    .sa_family      = AF_INET6,
    .sctp_xmit      = sctp_v6_xmit,
    .setsockopt      = sctp_v6_setsockopt,
    .getsockopt      = sctp_v6_getsockopt,
    .addr_valid      = sctp_v6_addr_valid,
    .cmp_addr        = sctp_v6_cmp_addr,
    .scope           = sctp_v6_scope,
    /* ... */
};
IPv6 멀티호밍 팁: IPv6 환경에서 멀티호밍을 사용할 때는 link-local 주소(fe80::)가 INIT에 포함되지 않도록 주의하세요. 다른 서브넷의 피어에게 link-local 주소를 알려주면 도달할 수 없어 Heartbeat가 계속 실패합니다. sctp_bindx로 글로벌 주소만 바인딩하는 것이 안전합니다.

SCTP 보안: 공격 방어 메커니즘

SCTP는 TCP의 보안 약점을 학습하여 설계 단계에서 여러 공격 방어 메커니즘을 내장했습니다. 4-way handshake의 State Cookie는 SYN Flood 방어, Verification Tag는 세션 하이재킹 방어, CRC32c 체크섬은 패킷 위조 감지에 각각 기여합니다.

SCTP vs TCP 보안 메커니즘 비교 TCP 취약점 SYN Flood 공격 대량 SYN → 서버 반열림 연결 소진 대응: SYN Cookie (옵션, 기능 제한) 세션 하이재킹 시퀀스 번호 예측 가능 → 패킷 주입 대응: 랜덤 ISN (여전히 32비트) RST(Reset) 공격 위조 RST → 연결 강제 종료 대응: BGP에서 MD5 서명 사용 약한 체크섬 TCP: 16비트 1의 보수 합 → 위조 쉬움 SCTP 내장 방어 State Cookie (4-Way Handshake) 서버 상태를 쿠키에 인코딩 → 서버 자원 0 클라이언트가 쿠키 반환해야 Association 생성 Verification Tag (32비트) INIT 시 랜덤 생성 → 모든 패킷에 포함 필수 VTag 불일치 패킷 즉시 폐기 ABORT 보호 (VTag 필수) 위조 ABORT에도 올바른 VTag 필요 VTag 모르면 ABORT 불가능 CRC32c 체크섬 32비트 CRC → 패킷 위조 탐지율 높음 SCTP는 프로토콜 설계 단계에서 TCP의 주요 보안 약점을 해결
공격 유형TCP 취약성SCTP 방어추가 보호
Flood 공격 (DoS) SYN → 반열림 연결 자원 소진 State Cookie → 서버 상태 0 INIT에서 서버 자원 할당 없음
세션 하이재킹 시퀀스 번호 32비트 예측 VTag 32비트 + TSN 32비트 = 64비트 AUTH 청크로 HMAC 인증 추가
연결 리셋 공격 RST 패킷 위조 → 연결 끊김 ABORT에도 VTag 필수 VTag 불일치 → 무시
패킷 위조 약한 16비트 체크섬 CRC32c (32비트) 위조 탐지율 ~99.99998%
Blind INIT Flood 해당 없음 INIT-ACK에 쿠키 포함 → 무상태 Cookie HMAC 서명으로 위조 방지
반사 공격 (Reflection) SYN-ACK 반사 INIT에 VTag=0 필수 → 반사 무효
ASCONF 하이재킹 해당 없음 AUTH 청크로 ASCONF 인증 인증 없는 ASCONF 차단 가능
/* State Cookie 보안 메커니즘 상세 */

/* 1. State Cookie 생성 (서버 측, INIT-ACK 전송 시) */
/* net/sctp/sm_make_chunk.c — sctp_pack_cookie() */
/*
 * Cookie 내용:
 *   - 초기화 파라미터 (스트림 수, a-rwnd 등)
 *   - 피어 주소 정보
 *   - 타임스탬프 (유효 기간 확인용)
 *   - HMAC 서명 (secret_key로 서명)
 *
 * 핵심: 서버가 어떤 상태도 저장하지 않음
 * → INIT Flood에도 서버 메모리 소진 불가
 */

/* 2. State Cookie 검증 (서버 측, COOKIE-ECHO 수신 시) */
/* net/sctp/sm_make_chunk.c — sctp_unpack_cookie() */
struct sctp_signed_cookie {
    __u8 signature[SCTP_SECRET_SIZE];  /* HMAC 서명 */
    __u32 __pad;
    struct sctp_cookie c;               /* 쿠키 본체 */
};
/*
 * 검증 순서:
 *   1. HMAC 서명 확인 (위조 방지)
 *   2. 타임스탬프 확인 (만료 방지, valid_cookie_life)
 *   3. 파라미터 추출 → Association 생성
 *
 * HMAC 키 회전: 커널은 2개의 secret_key를 번갈아 사용
 * → 키 전환 중에도 이전 키로 서명된 쿠키 유효
 */

/* 3. Verification Tag 검증 */
/* net/sctp/input.c — sctp_rcv() */
/*
 * 모든 수신 패킷에서:
 *   1. SCTP 공통 헤더의 VTag 추출
 *   2. Association의 peer.i.init_tag과 비교
 *   3. 불일치 → 패킷 즉시 폐기 (로그 없음, DoS 방지)
 *
 * 예외: INIT 청크는 VTag=0으로 전송
 * (아직 Association이 없으므로)
 */
/* CRC32c 체크섬 계산 — net/sctp/output.c */
static __be32 sctp_compute_cksum(
    const struct sk_buff *skb,
    unsigned int offset)
{
    /* CRC32c = Castagnoli CRC
     * TCP의 1의 보수 합과 달리:
     *   - 32비트 다항식 기반 오류 탐지
     *   - 하드웨어 가속 지원 (SSE4.2 CRC32 명령)
     *   - 위조 난이도: ~2^32 (TCP는 ~2^16)
     *
     * 계산 방법:
     *   1. 체크섬 필드를 0으로 설정
     *   2. 전체 SCTP 패킷에 CRC32c 적용
     *   3. 결과를 체크섬 필드에 저장
     */
    struct sctphdr *sh = sctp_hdr(skb);
    __le32 old = sh->checksum;
    __le32 new;

    sh->checksum = 0;
    new = sctp_csum_update(~(__u32)0, skb);
    sh->checksum = old;
    return cpu_to_le32(~new);
}
알려진 CVE 이력: SCTP 커널 구현에서 발견된 주요 취약점: CVE-2019-8956 (UAF in sctp_sendmsg), CVE-2017-5986 (panic in sctp_do_sm), CVE-2021-3655 (OOB read in sctp_rcv). 커널을 최신 LTS로 유지하고, 사용하지 않으면 modprobe -r sctp로 모듈을 언로드하여 공격 표면을 줄이세요.

SCTP 커널 패킷 경로 심화

SCTP 패킷이 커널에서 어떻게 처리되는지 수신(RX)과 송신(TX) 경로를 상세히 추적합니다. 각 단계의 함수 호출과 자료구조 변환을 이해하면 성능 병목과 버그를 효과적으로 분석할 수 있습니다.

SCTP 커널 패킷 경로 (수신 RX / 송신 TX) 수신 경로 (RX) NIC → NAPI → ip_rcv() sctp_rcv() — input.c CRC32c 검증 + VTag 확인 sctp_lookup_association() sctp_inq_push() → 입력 큐 sctp_do_sm() — 상태 머신 sctp_cmd_interpreter() — side effects sctp_ulpq_tail_data() → 수신 큐 sk_data_ready() → 사용자 공간 wakeup sctp_recvmsg() — 앱 수신 송신 경로 (TX) sctp_sendmsg() — 앱 전송 sctp_datamsg_from_user() sctp_outq_tail() → 출력 큐 sctp_outq_flush() — 스케줄링 sctp_packet_append_chunk() — 번들링 sctp_packet_transmit() CRC32c 계산 + IP 헤더 설정 ip_queue_xmit() / ip6_xmit() Netfilter → dev_queue_xmit() NIC 드라이버 → 물리 전송
단계함수소스 파일역할
RX 진입 sctp_rcv() net/sctp/input.c IP 계층에서 SCTP 패킷 수신, 청크 파싱 시작
Association 검색 sctp_lookup_association() net/sctp/input.c VTag + 포트로 해시 테이블에서 Association 찾기
상태 머신 sctp_do_sm() net/sctp/sm_sideeffect.c 이벤트 + 현재 상태 → 전이 함수 실행
부작용 실행 sctp_cmd_interpreter() net/sctp/sm_sideeffect.c 타이머 시작/정지, 청크 전송, 상태 전이
사용자 전달 sctp_ulpq_tail_data() net/sctp/ulpqueue.c 재조립 완료된 메시지를 소켓 수신 큐에 삽입
TX 시작 sctp_sendmsg() net/sctp/socket.c 사용자 데이터를 sctp_datamsg로 변환
출력 큐 sctp_outq_flush() net/sctp/outqueue.c 스케줄러로 전송할 청크 선택, 번들링
패킷 전송 sctp_packet_transmit() net/sctp/output.c SCTP 헤더/CRC32c 설정 → IP 계층 전달
/* 수신 경로 상세 — 청크별 처리 */

/* sctp_inq_pop(): 입력 큐에서 청크 하나씩 추출 */
struct sctp_chunk *sctp_inq_pop(
    struct sctp_inq *queue)
{
    /*
     * 1. sk_buff에서 다음 청크 헤더 파싱
     * 2. 청크 길이만큼 데이터 포인터 전진
     * 3. 패딩 건너뛰기 (4바이트 정렬)
     * 4. sctp_chunk 구조체에 래핑하여 반환
     *
     * 하나의 sk_buff(패킷)에 여러 청크가 번들링되어 있으면
     * 같은 sk_buff에서 여러 번 pop 가능
     */
}

/* 송신 경로 상세 — 데이터 청크 생성 */

/* sctp_datamsg_from_user(): 사용자 메시지 → 청크 분할 */
struct sctp_datamsg *sctp_datamsg_from_user(
    struct sctp_association *asoc,
    struct sctp_sndrcvinfo *sinfo,
    struct iov_iter *from)
{
    /*
     * 1. 메시지 크기 확인
     * 2. 경로 MTU 기준으로 분할 크기 결정
     *    max_data = PMTU - SCTP_HEADER - DATA_HEADER
     * 3. 첫 청크: B(Begin) 플래그 설정
     * 4. 중간 청크: B=0, E=0
     * 5. 마지막 청크: E(End) 플래그 설정
     * 6. TSN 할당 (글로벌 증가)
     * 7. SSN/MID 할당 (스트림별 증가)
     *
     * PR-SCTP: sinfo의 timetolive, pr_policy 반영
     */
}

/* sctp_outq_flush(): 번들링 전략 */
/*
 * 1. 제어 청크 우선 전송 (SACK, HEARTBEAT 등)
 * 2. 재전송 청크 전송 (Fast Retransmit 대상)
 * 3. 새 데이터 청크 전송 (스케줄러가 선택)
 * 4. 하나의 패킷에 여러 청크 번들링 시도
 *    조건: cwnd, a-rwnd, PMTU 여유 범위 내
 * 5. 패킷 가득 차면 sctp_packet_transmit() 호출
 */
성능 추적 팁: SCTP 패킷 경로의 병목을 찾으려면 ftrace로 함수별 실행 시간을 측정하세요. echo sctp_rcv sctp_do_sm sctp_outq_flush > /sys/kernel/debug/tracing/set_ftrace_filter로 핵심 함수만 필터링하면 오버헤드를 최소화하면서 전체 경로를 추적할 수 있습니다.

SCTP와 eBPF/XDP 통합

eBPF(extended Berkeley Packet Filter)는 커널 내부에서 안전하게 프로그램을 실행하여 SCTP의 관측성과 성능을 향상시킬 수 있습니다. kprobe/tracepoint를 통한 상세 추적부터 XDP를 통한 초기 패킷 필터링까지 다양한 활용이 가능합니다.

eBPF 유형연결 지점용도제약
kprobe 임의의 커널 함수 SCTP 함수 추적, 인자 검사 함수 서명 변경 시 깨질 수 있음
tracepoint sctp:sctp_probe 안정적인 SCTP 이벤트 추적 제공 tracepoint 수가 제한적
cgroup/sock_ops 소켓 연산 SCTP 소켓 옵션 자동 설정 SCTP 지원이 TCP만큼 풍부하지 않음
XDP NIC 수신 직후 SCTP 패킷 초기 필터링/리다이렉트 IP proto 132 수동 파싱 필요
tc (Traffic Control) ingress/egress SCTP 패킷 분류, 마킹 XDP보다 늦은 처리 단계
/* bpftrace를 사용한 SCTP 실시간 추적 */

/* 1. SCTP 패킷 수신 추적 */
/* $ bpftrace -e '
 *     kprobe:sctp_rcv {
 *         @rx_count = count();
 *         @rx_bytes = hist(((struct sk_buff *)arg0)->len);
 *     }
 *     interval:s:1 { print(@rx_count); print(@rx_bytes); }
 * '
 */

/* 2. SCTP 상태 전이 추적 */
/* $ bpftrace -e '
 *     kprobe:sctp_do_sm {
 *         @events[arg0, arg1] = count();
 *     }
 * '
 * arg0 = event_type (CHUNK, TIMEOUT, PRIMITIVE)
 * arg1 = subtype (INIT, DATA, SACK 등)
 */

/* 3. SCTP Association 생성/삭제 추적 */
/* $ bpftrace -e '
 *     kprobe:sctp_association_new {
 *         printf("New association: ep=%p\n", arg0);
 *     }
 *     kprobe:sctp_association_free {
 *         printf("Free association: asoc=%p\n", arg0);
 *     }
 * '
 */

/* 4. SCTP 재전송 모니터링 */
/* $ bpftrace -e '
 *     kprobe:sctp_retransmit_mark {
 *         @retransmits = count();
 *         printf("Retransmit in outqueue %p\n", arg0);
 *     }
 * '
 */

/* 5. SCTP Heartbeat 추적 */
/* $ bpftrace -e '
 *     kprobe:sctp_sf_sendbeat_8_3 {
 *         @hb_sent = count();
 *     }
 *     kprobe:sctp_sf_backbeat_8_3 {
 *         @hb_ack = count();
 *     }
 *     interval:s:10 {
 *         printf("HB sent: %d, HB-ACK: %d\n",
 *                @hb_sent, @hb_ack);
 *     }
 * '
 */
/* XDP 프로그램: SCTP 패킷 필터링 */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>

#define IPPROTO_SCTP 132

/* SCTP 공통 헤더 */
struct sctphdr {
    __be16 source;
    __be16 dest;
    __be32 vtag;
    __le32 checksum;
};

/* SCTP 청크 헤더 */
struct sctp_chunkhdr {
    __u8   type;
    __u8   flags;
    __be16 length;
};

SEC("xdp")
int xdp_sctp_filter(struct xdp_md *ctx)
{
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    /* Ethernet 헤더 */
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;

    /* IP 헤더 */
    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;
    if (ip->protocol != IPPROTO_SCTP)
        return XDP_PASS;

    /* SCTP 헤더 */
    struct sctphdr *sh = (void *)ip + (ip->ihl * 4);
    if ((void *)(sh + 1) > data_end)
        return XDP_PASS;

    /* 특정 포트의 SCTP만 허용 */
    __u16 dst_port = __constant_ntohs(sh->dest);
    if (dst_port != 9999 && dst_port != 36412)
        return XDP_DROP;  /* 허용 포트 외 SCTP 차단 */

    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";
# XDP SCTP 필터 로드 및 테스트
$ clang -O2 -target bpf -c xdp_sctp_filter.c -o xdp_sctp_filter.o
$ ip link set dev eth0 xdpgeneric obj xdp_sctp_filter.o sec xdp

# 통계 확인
$ bpftool prog show
$ bpftool map dump name xdp_stats

# 제거
$ ip link set dev eth0 xdpgeneric off
SCTP tracepoint: 리눅스 커널은 sctp:sctp_probe tracepoint를 제공합니다. perf trace -e sctp:*로 SCTP 이벤트를 추적할 수 있으며, /sys/kernel/debug/tracing/events/sctp/에서 사용 가능한 tracepoint를 확인하세요. kprobe와 달리 tracepoint는 커널 버전 간 안정적인 인터페이스를 제공합니다.

SCTP vs QUIC: 현대 전송 프로토콜 비교

SCTP와 QUIC는 모두 TCP의 한계를 극복하기 위해 설계된 전송 프로토콜이지만, 접근 방식이 근본적으로 다릅니다. SCTP는 커널 프로토콜 스택으로 구현되고, QUIC는 사용자 공간에서 UDP 위에 구축됩니다. 두 프로토콜의 설계 철학과 실무 배포 현황을 비교합니다.

SCTP vs QUIC 프로토콜 스택 비교 SCTP 스택 애플리케이션 SCTP (커널 모듈) IP (커널) NIC 드라이버 (커널) IP Proto 132 커널 공간에서 완전 구현 syscall 오버헤드 최소 NAT/방화벽 호환성 낮음 QUIC 스택 애플리케이션 QUIC + TLS 1.3 (사용자 공간) UDP (커널) IP (커널) → NIC UDP Port 443 사용자 공간에서 프로토콜 구현 빠른 업데이트/배포 가능 NAT/방화벽 완벽 호환 (UDP) 공통 목표 멀티스트림 (HoL 해결) 연결 마이그레이션 0-RTT / 빠른 연결
비교 항목SCTPQUIC
표준 RFC 4960 (2007, 원본 RFC 2960은 2000) RFC 9000 (2021)
구현 위치 커널 프로토콜 스택 사용자 공간 라이브러리 (UDP 위)
IP 프로토콜 132 (독자 프로토콜) 17 (UDP)
NAT 통과 UDP 캡슐화 필요 (RFC 6951) UDP 기반 → 자연 통과
암호화 DTLS 또는 별도 설정 필요 TLS 1.3 내장 (필수)
멀티스트리밍 지원 (INIT 시 스트림 수 협상) 지원 (스트림 수 제한 없음)
멀티호밍 네이티브 지원 (자동 failover) Connection Migration (수동)
메시지 경계 보존 (메시지 지향) 스트림은 바이트 스트림
부분 신뢰성 PR-SCTP (RFC 3758) 없음 (모든 스트림 신뢰적)
연결 설정 4-way handshake (1-RTT) 0-RTT 또는 1-RTT
업데이트 용이성 커널 업그레이드 필요 앱과 함께 배포
주요 사용처 텔레콤 (Diameter, S1AP, NGAP), WebRTC DC 웹 (HTTP/3), 범용
리눅스 커널 지원 net/sctp/ 모듈 커널 QUIC (개발 중, 6.x~)
## SCTP가 여전히 QUIC보다 적합한 경우

1. 텔레콤 시그널링 (Diameter, SIGTRAN, S1AP/NGAP)
   - 3GPP 표준이 SCTP를 명시적으로 요구
   - 멀티호밍 failover가 99.999% 가용성 핵심
   - 메시지 경계가 시그널링 프로토콜에 필수

2. 고가용성 클러스터 (Corosync, Pacemaker)
   - 커널 레벨 멀티호밍으로 NIC 장애 자동 복구
   - 사용자 공간 라이브러리 의존 없음

3. WebRTC DataChannel
   - SCTP over DTLS over UDP (usrsctp)
   - 부분 신뢰성(PR-SCTP)이 실시간 데이터에 필수
   - 순서/비순서 전송 혼합 사용

## QUIC가 SCTP보다 적합한 경우

1. 웹 트래픽 (HTTP/3)
   - 브라우저 기본 지원
   - NAT/방화벽/CDN 완벽 호환

2. 인터넷 공개 서비스
   - UDP 기반으로 미들박스 통과
   - TLS 1.3 필수 암호화

3. 모바일 앱 통신
   - Connection Migration으로 네트워크 전환 지원
   - 사용자 공간 구현으로 앱에 번들 가능
SCTP의 미래: SCTP는 텔레콤 도메인에서 여전히 필수적이지만, 범용 인터넷에서는 QUIC가 빠르게 자리잡고 있습니다. 5G 코어(NGAP/XnAP)에서 SCTP 사용이 계속되고, WebRTC DataChannel의 SCTP 기반도 유지될 전망입니다. 커널 QUIC 구현(KQUIC)이 진행 중이지만, SCTP의 멀티호밍과 메시지 지향 특성은 QUIC로 대체하기 어려운 고유 영역입니다.

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

참고자료