SCTP 프로토콜
Linux SCTP(Stream Control Transmission Protocol) 구현을 심층 설명합니다. 멀티스트리밍·멀티호밍 기반 Association 모델, 초기 4-way handshake, Path 관리와 failover, 메시지 단위 전송 보장, TCP/UDP 대비 설계 차이, 커널 자료구조와 타이머 동작, 통신 장애·재전송·경로 전환 이슈에 대한 운영 디버깅 포인트까지 다룹니다.
핵심 요약
- Association — TCP 연결과 유사하지만 쿠키 기반 4-way handshake
- Multi-streaming — 독립 스트림으로 HoL blocking 완화
- Multi-homing — 다중 경로 + heartbeat failover
- Chunk — 제어/데이터를 하나의 패킷 구조로 구성
- WebRTC DataChannel — 실무 대표 활용 사례
단계별 이해
- 연결 모델
4-way handshake와 state cookie 방식을 먼저 이해합니다. - 스트림 모델
스트림 번호 기반 데이터 분리를 확인합니다. - 경로 모델
멀티호밍에서 primary/backup 경로 전환을 살펴봅니다. - 운영 모델
sysctl과 소켓 옵션으로 타임아웃/재전송 정책을 조정합니다.
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
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 | 내용 |
|---|---|---|
| 2000 | RFC 2960 | SCTP 최초 표준화 |
| 2004 | RFC 3758 | PR-SCTP (부분 신뢰성) 확장 |
| 2004 | RFC 3873 | SCTP MIB (관리 정보 베이스) |
| 2007 | RFC 4820 | Padding Chunk |
| 2007 | RFC 4895 | AUTH 청크 (인증) |
| 2007 | RFC 4960 | SCTP 개정판 (현재 주요 표준) |
| 2007 | RFC 5061 | 동적 주소 재설정 (ASCONF) |
| 2011 | RFC 6458 | 소켓 API 확장 (sendv/recvv) |
| 2013 | RFC 6951 | UDP 캡슐화 (NAT 통과) |
| 2017 | RFC 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 핵심 특성
SCTP는 TCP와 UDP의 장점을 결합하면서도 각각의 한계를 극복하도록 설계되었습니다. 다음 표는 세 프로토콜의 핵심 특성을 비교합니다.
| 특성 | TCP | UDP | SCTP |
|---|---|---|---|
| 연결 지향 | 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 |
| 혼잡 제어 | 단일 경로 | 없음 | 경로별 독립 혼잡 제어 |
SCTP 데이터 흐름
SCTP 데이터 전송은 애플리케이션 메시지를 청크(Chunk) 단위로 분할하고, TSN(Transmission Sequence Number)을 부여하여 신뢰적으로 전달합니다. 수신 측은 SACK 청크로 수신 상태를 알려주며, 송신 측은 이를 기반으로 재전송을 결정합니다.
위 다이어그램에서 핵심 포인트는 다음과 같습니다:
- 단편화: 메시지가 경로 MTU보다 클 경우 여러 DATA 청크로 분할합니다. B/E 플래그로 첫/마지막 단편을 표시합니다.
- 번들링: 여러 DATA 청크를 하나의 SCTP 패킷에 묶어 전송 효율을 높입니다.
- TSN 기반 확인: 각 DATA 청크에 고유 TSN이 부여되며, 수신 측은 SACK로 수신된 TSN과 Gap을 보고합니다.
- 재전송: SACK의 Gap Report를 통해 손실된 청크를 선택적으로 재전송합니다.
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로 이 쿠키를 되돌려보내야만 서버가 상태를 할당합니다.
| 단계 | 방향 | 청크 | 포함 정보 | 서버 상태 |
|---|---|---|---|---|
| 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;
}
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 즉시 종료, 미전송 데이터 폐기 */
close()는 미전송 데이터를 모두 보낸 후 SHUTDOWN 시퀀스를 수행합니다 (graceful).
SCTP_ABORT 플래그는 즉시 ABORT 청크를 보내고 Association을 파괴합니다 (ungraceful).
SO_LINGER 옵션으로 graceful 종료의 대기 시간을 제한할 수 있습니다.
멀티스트리밍
멀티스트리밍은 SCTP의 가장 혁신적인 기능 중 하나입니다. 하나의 Association 내에 여러 독립적인 스트림을 두어, 한 스트림에서 패킷 손실이 발생해도 다른 스트림의 데이터 전달에 영향을 주지 않습니다. 이는 TCP에서 발생하는 Head-of-Line(HoL) blocking 문제를 프로토콜 수준에서 해결합니다.
각 스트림은 자체적인 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) 경로로 전환합니다.
| 경로 상태 | 상수 | 설명 | 전환 조건 |
|---|---|---|---|
| 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,
¶ms, 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(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로 이루어집니다. 제어 청크와 데이터 청크가 하나의 패킷에 함께 번들링될 수 있습니다.
| 청크 | 타입 | 용도 | RFC |
|---|---|---|---|
| DATA | 0 | 사용자 데이터 전달 (TSN, 스트림 번호, 시퀀스 번호 포함) | 4960 |
| INIT | 1 | Association 시작 요청 | 4960 |
| INIT ACK | 2 | INIT 응답 + State Cookie | 4960 |
| SACK | 3 | 선택적 확인응답 (TCP SACK와 유사) | 4960 |
| HEARTBEAT | 4 | 경로 활성 확인 (멀티호밍) | 4960 |
| HEARTBEAT ACK | 5 | Heartbeat 응답 | 4960 |
| ABORT | 6 | Association 즉시 종료 | 4960 |
| SHUTDOWN | 7 | 정상 종료 시작 | 4960 |
| SHUTDOWN ACK | 8 | SHUTDOWN 확인 | 4960 |
| ERROR | 9 | 오류 보고 (원인 코드 포함) | 4960 |
| COOKIE ECHO | 10 | State Cookie 반환 (handshake 3단계) | 4960 |
| COOKIE ACK | 11 | Cookie 확인 (handshake 4단계) | 4960 |
| ECNE | 12 | ECN Echo | 4960 |
| CWR | 13 | Congestion Window Reduced | 4960 |
| SHUTDOWN COMPLETE | 14 | 종료 완료 | 4960 |
| AUTH | 15 | 인증 청크 (HMAC) | 4895 |
| ASCONF ACK | 128 | 주소 설정 변경 확인 | 5061 |
| RE-CONFIG | 130 | 스트림 재설정 | 6525 |
| PAD | 132 | 패딩 | 4820 |
| FORWARD TSN | 192 | 수신 불필요한 TSN 건너뛰기 (부분 신뢰성) | 3758 |
| ASCONF | 193 | 주소 설정 변경 요청 | 5061 |
| I-DATA | 64 | 인터리빙 데이터 (MID 기반) | 8260 |
| I-FORWARD-TSN | 194 | 인터리빙용 FORWARD TSN | 8260 |
/* 청크 공통 헤더 — 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를 로드해야 하며, lksctp-tools 패키지가 사용자 공간 유틸리티를 제공합니다.
청크 번들링과 단편화
SCTP는 효율적인 전송을 위해 두 가지 메커니즘을 제공합니다. 번들링(Bundling)은 여러 작은 메시지를 하나의 SCTP 패킷에 묶어 전송하며, 단편화(Fragmentation)는 큰 메시지를 경로 MTU에 맞게 분할합니다.
/* 번들링 비활성화 (지연 민감 애플리케이션) */
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 필드 | 크기 | 설명 |
|---|---|---|
| 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 업데이트하여 흐름 제어에 반영
*/
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 교환을 사용합니다.
| 상태 | enum 값 | 설명 | 진입 조건 |
|---|---|---|---|
| CLOSED | SCTP_STATE_CLOSED | 초기/종료 상태 | 시작 또는 종료 완료 |
| COOKIE_WAIT | SCTP_STATE_COOKIE_WAIT | INIT 전송 후 대기 | INIT 전송 |
| COOKIE_ECHOED | SCTP_STATE_COOKIE_ECHOED | COOKIE-ECHO 전송 후 대기 | INIT-ACK 수신 |
| ESTABLISHED | SCTP_STATE_ESTABLISHED | 데이터 교환 가능 | COOKIE-ACK 수신/전송 |
| SHUTDOWN_PENDING | SCTP_STATE_SHUTDOWN_PENDING | 종료 요청, 미전송 데이터 있음 | close() 호출 |
| SHUTDOWN_SENT | SCTP_STATE_SHUTDOWN_SENT | SHUTDOWN 전송 완료 | 미전송 데이터 완료 |
| SHUTDOWN_RECEIVED | SCTP_STATE_SHUTDOWN_RECEIVED | 상대의 SHUTDOWN 수신 | SHUTDOWN 청크 수신 |
| SHUTDOWN_ACK_SENT | SCTP_STATE_SHUTDOWN_ACK_SENT | SHUTDOWN-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를 관리합니다.
| 파라미터 | 초기값 | 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;
}
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 정책 | 플래그 | 동작 | 사용 사례 |
|---|---|---|---|
| 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; /* 재전송 횟수 */
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로 이 기능을 제어합니다.
# 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));
커널 소스 구조
리눅스 커널의 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)
*/
ftrace로 sctp_do_sm 함수를 추적하거나,
net/sctp/의 pr_debug() 메시지를 활성화하면 됩니다.
커널 빌드 시 CONFIG_SCTP_DBG_MSG=y를 설정하면 상세 디버그 로그를 볼 수 있습니다.
커널 자료구조
리눅스 커널의 SCTP 구현은 여러 계층의 자료구조로 이루어져 있습니다.
최상위 sctp_sock에서 시작하여 sctp_endpoint, sctp_association,
sctp_transport까지 이어지는 계층 구조를 이해하면 커널 코드를 읽기 쉽습니다.
/* 핵심 자료구조 요약 — 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 | UDP | SCTP |
|---|---|---|---|
| IP 프로토콜 번호 | 6 | 17 | 132 |
| 데이터 단위 | 바이트 스트림 | 데이터그램 | 메시지 (청크) |
| 연결 설정 | 3-way handshake | 없음 | 4-way handshake |
| 연결 종료 | 4-way (FIN/ACK) | 없음 | 3-way (SHUTDOWN) |
| 멀티호밍 | 불가능 | 불가능 | 지원 (자동 failover) |
| 멀티스트리밍 | 불가능 | 불가능 | 지원 (독립 스트림) |
| HoL Blocking | 발생 | 해당 없음 | 스트림 독립으로 방지 |
| 메시지 경계 | 보존하지 않음 | 보존 | 보존 |
| 혼잡 제어 | 단일 경로 | 없음 | 경로별 독립 |
| 부분 신뢰성 | 불가능 | 해당 없음 | PR-SCTP 지원 |
| NAT 통과 | 자연 지원 | 자연 지원 | UDP 캡슐화 필요 |
| 커널 기본 지원 | 항상 내장 | 항상 내장 | 모듈 로드 필요 |
| 대표 사용 사례 | HTTP, SSH, DB | DNS, 게임, VoIP | 텔레콤, WebRTC |
| 사용 사례 | 권장 프로토콜 | 이유 |
|---|---|---|
| 웹 서비스 (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
트러블슈팅
# 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) 소진 | 수신 측 처리 속도 개선, 버퍼 증가 |
/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 4960 - Stream Control Transmission Protocol
- RFC 6458 - Sockets API Extensions for SCTP
- RFC 3758 - SCTP Partial Reliability Extension (PR-SCTP)
- RFC 4895 - Authenticated Chunks for SCTP
- RFC 6951 - UDP Encapsulation of SCTP Packets
- RFC 8260 - SCTP Stream Schedulers and User Message Interleaving
- RFC 5061 - SCTP Dynamic Address Reconfiguration
- Linux Kernel Documentation: SCTP
- Linux Kernel Source: net/sctp
- lksctp-tools - Linux Kernel SCTP Tools