Skip to content

Split View: TCP/IP & 네트워크 프로그래밍 심화 가이드 2025: 소켓, HTTP/3, QUIC, DNS, TLS 1.3

✨ Learn with Quiz
|

TCP/IP & 네트워크 프로그래밍 심화 가이드 2025: 소켓, HTTP/3, QUIC, DNS, TLS 1.3

목차

1. 왜 네트워크 프로그래밍 심화 학습이 필요한가

현대 소프트웨어 엔지니어에게 네트워크는 더 이상 "인프라팀이 알아서 하는 영역"이 아닙니다. 마이크로서비스 간 통신 지연이 P99 레이턴시를 좌우하고, HTTP/3 도입 여부가 모바일 사용자 경험을 결정하며, TLS 설정 하나가 보안 감사 통과 여부를 가릅니다.

이 가이드에서 다루는 핵심 주제:

  • TCP 내부 동작 원리와 상태 머신
  • 혼잡 제어 알고리즘(Cubic, BBR, BBRv2)
  • 소켓 프로그래밍과 I/O 멀티플렉싱(epoll, kqueue, io_uring)
  • HTTP/1.1 → HTTP/2 → HTTP/3(QUIC) 진화
  • DNS 해석 과정과 보안(DNSSEC, DoH/DoT)
  • TLS 1.3 핸드셰이크와 0-RTT
  • 네트워크 디버깅 도구(tcpdump, Wireshark, ss)
  • 성능 튜닝(Nagle, TCP_NODELAY, 커널 파라미터)

2. TCP 프로토콜 심화

2.1 3-Way 핸드셰이크

TCP 연결 수립의 핵심 과정입니다.

Client                    Server
  |                         |
  |--- SYN (seq=x) ------->|   (1) 클라이언트가 연결 요청
  |                         |
  |<-- SYN-ACK ------------|   (2) 서버가 요청 수락 + 자신의 SYN
  |    (seq=y, ack=x+1)    |
  |                         |
  |--- ACK (ack=y+1) ----->|   (3) 클라이언트가 서버의 SYN 확인
  |                         |
  |==== 연결 수립 완료 ====|

왜 3-way인가? 양쪽 모두 상대방의 초기 시퀀스 번호(ISN)를 확인해야 하기 때문입니다. 2-way로는 서버가 클라이언트의 ACK을 확인할 수 없습니다.

// 서버 소켓 생성 예제
int server_fd = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(8080);

bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));

// backlog: SYN 큐 + Accept 큐 크기 결정
listen(server_fd, SOMAXCONN);

// 3-way 핸드셰이크 완료된 연결을 accept 큐에서 꺼냄
int client_fd = accept(server_fd, NULL, NULL);

2.2 4-Way 종료

연결 종료는 양방향 독립적으로 이루어집니다.

Client                    Server
  |                         |
  |--- FIN (seq=u) ------->|   (1) 클라이언트: "나는 보낼 데이터 없음"
  |                         |
  |<-- ACK (ack=u+1) ------|   (2) 서버: "알겠음" (서버는 아직 보낼 수 있음)
  |                         |
  |    ... 서버가 남은 데이터 전송 ...
  |                         |
  |<-- FIN (seq=w) --------|   (3) 서버: "나도 보낼 데이터 없음"
  |                         |
  |--- ACK (ack=w+1) ----->|   (4) 클라이언트: "알겠음"
  |                         |
  |=== TIME_WAIT (2MSL) ===|   클라이언트는 TIME_WAIT 상태 진입

2.3 TCP 상태 머신

                          CLOSED
                            |
                      (능동 OPEN)
                            |
                        SYN_SENT
                            |
                      (SYN-ACK 수신)
                            |
                       ESTABLISHED
                          /    \
                   (능동 CLOSE) (수동 CLOSE)
                        /        \
                  FIN_WAIT_1    CLOSE_WAIT
                      |              |
                  FIN_WAIT_2    LAST_ACK
                      |              |
                  TIME_WAIT      CLOSED
                      |
                   CLOSED

2.4 TIME_WAIT과 SO_REUSEADDR

TIME_WAIT 상태는 2MSL(Maximum Segment Lifetime, 보통 60초) 동안 유지됩니다.

TIME_WAIT이 필요한 이유:

  1. 지연된 패킷이 새 연결에 영향을 주는 것 방지
  2. 마지막 ACK 손실 시 재전송 가능
// 서버 재시작 시 "Address already in use" 방지
int optval = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR,
           &optval, sizeof(optval));

// Linux에서는 SO_REUSEPORT도 사용 가능 (로드밸런싱)
setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT,
           &optval, sizeof(optval));

TIME_WAIT 과다 시 대응:

# TIME_WAIT 소켓 수 확인
ss -s | grep TIME-WAIT

# 커널 파라미터 조정 (주의: 부작용 가능)
# net.ipv4.tcp_tw_reuse = 1 허용
sysctl -w net.ipv4.tcp_tw_reuse=1

# TIME_WAIT 소켓 최대 수
sysctl -w net.ipv4.tcp_max_tw_buckets=262144

3. TCP 혼잡 제어(Congestion Control)

3.1 왜 혼잡 제어가 필요한가

네트워크 혼잡 시 모든 호스트가 무한정 패킷을 보내면 라우터 버퍼가 넘쳐 패킷 손실이 발생하고, 재전송이 혼잡을 더 악화시키는 혼잡 붕괴(Congestion Collapse) 가 발생합니다.

3.2 기본 알고리즘

cwnd (Congestion Window)
  ^
  |                    * * * *
  |                  *         *   <-- Congestion Avoidance (선형 증가)
  |                *             *
  |              *                 \
  |            *                    \  패킷 손실 감지
  |          *                       \
  |        *   <-- Slow Start         * (cwnd 절반으로)
  |      *     (지수 증가)             *
  |    *                                *
  |  *                                   * ...
  |*                                      
  +-----------------------------------------> 시간
       ssthresh

Slow Start: cwnd를 1 MSS에서 시작, ACK마다 1 MSS 증가 (RTT마다 2배)

Congestion Avoidance: cwnd가 ssthresh에 도달하면 RTT마다 1 MSS씩 선형 증가

Fast Retransmit: 3개의 중복 ACK 수신 시 타임아웃 기다리지 않고 즉시 재전송

Fast Recovery: 패킷 손실 시 cwnd를 1이 아닌 ssthresh의 절반으로 설정

3.3 Cubic vs BBR

알고리즘 비교:

+----------+--------+-----------+-------------------------------+
| 알고리즘  | 타입   | 손실 감지  | 특징                           |
+----------+--------+-----------+-------------------------------+
| Reno     | 손실   | 패킷 손실  | 기본 AIMD                      |
| Cubic    | 손실   | 패킷 손실  | 3차 함수, Linux 기본값           |
| BBR      | 모델   | RTT/BW   | 대역폭-지연 모델, Google 개발    |
| BBRv2    | 모델   | RTT/BW   | BBR 개선, 공정성 향상            |
+----------+--------+-----------+-------------------------------+

Cubic: Linux 기본 혼잡 제어. 윈도우 크기가 3차 함수(cubic function)를 따릅니다.

# 현재 혼잡 제어 알고리즘 확인
sysctl net.ipv4.tcp_congestion_control
# 결과: net.ipv4.tcp_congestion_control = cubic

# BBR로 변경
sysctl -w net.ipv4.tcp_congestion_control=bbr

# 사용 가능한 알고리즘 목록
sysctl net.ipv4.tcp_available_congestion_control

BBR (Bottleneck Bandwidth and RTT):

  • 패킷 손실이 아닌 실제 대역폭과 RTT를 측정하여 최적 전송 속도 결정
  • 높은 손실률 환경(위성, 무선)에서 Cubic보다 월등한 성능
  • 단점: 다른 흐름과의 공정성 문제
# BBR 활성화 (커널 4.9+)
modprobe tcp_bbr
echo "tcp_bbr" >> /etc/modules-load.d/bbr.conf
sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr

4. UDP: 언제, 왜 사용하는가

4.1 TCP vs UDP

+------------+----------------------------+----------------------------+
| 특성        | TCP                        | UDP                        |
+------------+----------------------------+----------------------------+
| 연결        | 연결 지향 (3-way)           | 비연결                      |
| 신뢰성      | 보장 (재전송, 순서)          | 보장 없음                   |
| 흐름 제어    | 있음 (슬라이딩 윈도우)       | 없음                        |
| 혼잡 제어    | 있음                        | 없음 (앱이 직접 구현)        |
| 헤더 크기    | 20-60 바이트                | 8 바이트                    |
| 지연        | 높음 (핸드셰이크+재전송)     | 낮음                        |
| 사용 사례    | HTTP, SSH, DB              | DNS, 게임, 스트리밍, QUIC    |
+------------+----------------------------+----------------------------+

4.2 UDP 활용 사례

게임: 프레임 단위 위치 데이터는 최신 값만 중요. 재전송보다 다음 프레임이 중요합니다.

# 간단한 UDP 게임 서버
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', 9999))

players = {}
while True:
    data, addr = sock.recvfrom(1024)
    # 플레이어 위치 업데이트 (손실 허용)
    players[addr] = parse_position(data)
    
    # 모든 플레이어에게 게임 상태 브로드캐스트
    state = serialize_game_state(players)
    for player_addr in players:
        sock.sendto(state, player_addr)

DNS: 단일 요청-응답 패턴. TCP 핸드셰이크 오버헤드가 불필요합니다.

실시간 스트리밍: RTP/RTCP 프로토콜은 UDP 위에 동작합니다.


5. 소켓 프로그래밍

5.1 Berkeley 소켓 API

// TCP 클라이언트 전체 예제
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    // 1. 소켓 생성
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        return 1;
    }
    
    // 2. 서버 주소 설정
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(80);
    inet_pton(AF_INET, "93.184.216.34", &server_addr.sin_addr);
    
    // 3. 연결 (3-way 핸드셰이크 발생)
    if (connect(sock, (struct sockaddr *)&server_addr,
                sizeof(server_addr)) < 0) {
        perror("connect");
        return 1;
    }
    
    // 4. HTTP 요청 전송
    const char *request = "GET / HTTP/1.1\r\n"
                          "Host: example.com\r\n"
                          "Connection: close\r\n\r\n";
    send(sock, request, strlen(request), 0);
    
    // 5. 응답 수신
    char buffer[4096];
    int bytes;
    while ((bytes = recv(sock, buffer, sizeof(buffer) - 1, 0)) > 0) {
        buffer[bytes] = '\0';
        printf("%s", buffer);
    }
    
    // 6. 연결 종료 (4-way 종료 발생)
    close(sock);
    return 0;
}

5.2 Blocking vs Non-Blocking

#include <fcntl.h>

// Non-blocking 모드 설정
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);

// Non-blocking connect
int ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0 && errno == EINPROGRESS) {
    // 연결 진행 중 - poll/epoll로 완료 확인 필요
    struct pollfd pfd;
    pfd.fd = sock;
    pfd.events = POLLOUT;
    poll(&pfd, 1, 5000);  // 5초 타임아웃
    
    int error;
    socklen_t len = sizeof(error);
    getsockopt(sock, SOL_LEVEL, SO_ERROR, &error, &len);
    if (error == 0) {
        // 연결 성공
    }
}

// Non-blocking recv
char buf[1024];
ssize_t n = recv(sock, buf, sizeof(buf), 0);
if (n < 0) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
        // 데이터 아직 없음 - 나중에 다시 시도
    } else {
        // 실제 에러
    }
}

6. I/O 멀티플렉싱

6.1 select, poll, epoll, kqueue, io_uring 비교

+------------+--------+-----------+----------+----------------------------+
| 메커니즘    | 플랫폼  | 시간복잡도 | FD 제한   | 특징                        |
+------------+--------+-----------+----------+----------------------------+
| select     | 모든 OS | O(n)      | 1024     | 가장 오래됨, 이식성 높음      |
| poll       | 모든 OS | O(n)      | 없음     | select 개선, 여전히 O(n)     |
| epoll      | Linux  | O(1)      | 없음     | 이벤트 기반, 대규모 연결에 적합 |
| kqueue     | BSD    | O(1)      | 없음     | macOS/FreeBSD, 기능 풍부     |
| io_uring   | Linux  | O(1)      | 없음     | 5.1+, 비동기 I/O 혁명        |
+------------+--------+-----------+----------+----------------------------+

6.2 epoll 심화

#include <sys/epoll.h>

#define MAX_EVENTS 1024

int epoll_fd = epoll_create1(0);

// 서버 소켓 등록
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = server_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);

struct epoll_event events[MAX_EVENTS];

while (1) {
    // 이벤트 대기 (블로킹)
    int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
    
    for (int i = 0; i < nfds; i++) {
        if (events[i].data.fd == server_fd) {
            // 새 연결 수락
            int client_fd = accept(server_fd, NULL, NULL);
            
            // Non-blocking으로 설정
            fcntl(client_fd, F_SETFL,
                  fcntl(client_fd, F_GETFL, 0) | O_NONBLOCK);
            
            // Edge-Triggered 모드로 등록
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = client_fd;
            epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
        } else {
            // 클라이언트 데이터 처리
            handle_client(events[i].data.fd);
        }
    }
}

Level-Triggered vs Edge-Triggered:

  • LT (기본): 데이터가 있으면 계속 알림. 안전하지만 불필요한 시스템 콜 발생
  • ET: 상태 변경 시에만 알림. 성능 좋지만 한 번에 모든 데이터를 읽어야 함
// Edge-Triggered에서 데이터 완전히 읽기
void handle_client_et(int fd) {
    char buf[4096];
    while (1) {
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n < 0) {
            if (errno == EAGAIN) break;  // 모든 데이터 읽음
            perror("read");
            break;
        }
        if (n == 0) {
            // 연결 종료
            close(fd);
            break;
        }
        process_data(buf, n);
    }
}

6.3 io_uring (Linux 5.1+)

#include <liburing.h>

struct io_uring ring;
io_uring_queue_init(256, &ring, 0);

// 읽기 요청 제출 (비동기)
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, client_fd, buf, sizeof(buf), 0);
io_uring_sqe_set_data(sqe, client_ctx);
io_uring_submit(&ring);

// 완료 이벤트 수확
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);

struct client_ctx *ctx = io_uring_cqe_get_data(cqe);
int bytes_read = cqe->res;
// 처리...

io_uring_cqe_seen(&ring, cqe);

io_uring의 혁신:

  • 시스템 콜 없이 커널과 통신 (공유 링 버퍼)
  • 배치 제출/완료로 오버헤드 최소화
  • 파일 I/O, 네트워크 I/O, 타이머 모두 지원
  • epoll 대비 최대 2-3배 처리량 향상

7. HTTP 프로토콜 진화

7.1 HTTP/1.1

HTTP/1.1의 핵심 기능:

1. Keep-Alive (지속 연결)
   - 하나의 TCP 연결로 여러 요청/응답 처리
   - Connection: keep-alive (기본값)

2. Pipelining (파이프라이닝)
   - 응답을 기다리지 않고 여러 요청 전송
   - 실제로는 HOL Blocking 문제로 거의 비활성화

3. Chunked Transfer Encoding
   - Content-Length 없이 스트리밍 응답 가능

HTTP/1.1의 한계 - Head-of-Line Blocking:

Client              Server
  |-- GET /a -------->|
  |                   |  (응답 a 생성에 3)
  |-- GET /b -------->|
  |                   |  b는 준비됐지만 a가 끝날 때까지 대기
  |<-- Response /a ---|
  |<-- Response /b ---|  (불필요한 지연)

실무에서는 도메인 샤딩(6-8개 TCP 연결)으로 우회했습니다.

7.2 HTTP/2

HTTP/2 핵심 기능:

1. 멀티플렉싱 (Multiplexing)
   - 하나의 TCP 연결에서 여러 스트림 동시 전송
   - 각 스트림은 독립적 (HOL Blocking 해결... TCP 레벨 제외)

2. HPACK 헤더 압축
   - 정적/동적 테이블로 헤더 중복 제거
   - 첫 요청: "content-type: application/json" (전체)
   - 다음 요청: 인덱스 번호만 전송

3. Server Push
   - HTML 요청 시 CSS/JS를 미리 푸시
   - 실무에서는 캐시 문제로 거의 사용 안 함

4. 바이너리 프레이밍
   - 텍스트가 아닌 바이너리 프레임
   - 파싱 효율성 향상
HTTP/2 프레임 구조:

+-----------------------------------------------+
| Length (24)                                    |
+---------------+-------------------------------+
| Type (8)      | Flags (8)                     |
+---------------+-------------------------------+
| Reserved (1)  | Stream Identifier (31)        |
+-----------------------------------------------+
| Frame Payload                                 |
+-----------------------------------------------+

스트림 ID:
  - 홀수: 클라이언트 시작
  - 짝수: 서버 시작 (Server Push)
  - 0: 제어 프레임

7.3 HTTP/3 (QUIC)

HTTP 버전별 비교:

+----------+----------+---------+---------+----------------------------+
| 버전      | 전송 계층 | 암호화   | RTT    | HOL Blocking               |
+----------+----------+---------+---------+----------------------------+
| HTTP/1.1 | TCP      | 선택    | 2-3 RTT | 있음 (응답 레벨)            |
| HTTP/2   | TCP      | 사실상  | 2-3 RTT | 있음 (TCP 레벨)             |
| HTTP/3   | QUIC/UDP | 필수    | 1 RTT   | 없음 (스트림 독립)           |
|          |          |         | (0 RTT) |                            |
+----------+----------+---------+---------+----------------------------+

8. QUIC 프로토콜 심화

8.1 QUIC 아키텍처

전통적 스택:          QUIC 스택:

+----------+          +----------+
| HTTP/2   |          | HTTP/3   |
+----------+          +----------+
| TLS 1.2+ |          | QUIC     |  <- TLS 1.3 내장
+----------+          +----------+
| TCP      |          | UDP      |
+----------+          +----------+
| IP       |          | IP       |
+----------+          +----------+

8.2 0-RTT 연결

연결 (1-RTT):

Client                    Server
  |                         |
  |--- Initial (CHLO) ---->|   ClientHello + 전송 파라미터
  |                         |
  |<-- Initial (SHLO) -----|   ServerHello + 인증서 + 전송 파라미터
  |                         |
  |--- Handshake Done ----->|
  |                         |
  |=== 데이터 전송 시작 ====|   1-RTT 만에 연결!

재연결 (0-RTT):

Client                    Server
  |                         |
  |--- Initial + 0-RTT --->|   이전 세션 키로 암호화된 데이터 포함
  |    데이터              |
  |                         |   서버는 즉시 데이터 처리 가능
  |<-- Handshake ----------|
  |                         |
  |=== 즉시 데이터 교환 ====|   0-RTT!

0-RTT 보안 주의사항: 리플레이 공격에 취약합니다. 멱등성(idempotent) 요청만 0-RTT로 보내야 합니다.

8.3 연결 마이그레이션

Wi-Fi에서 셀룰러로 전환 시:

TCP:IP 주소 -> 새 연결 필요 -> 3-way 핸드셰이크 + TLS 다시
QUIC: Connection ID 기반 -> IP 변경돼도 연결 유지!

Client (Wi-Fi: 192.168.1.10)  ---QUIC (CID: abc123)--->  Server
         |
    (Wi-Fi 끊김, 셀룰러 전환)
         |
Client (셀룰러: 10.0.0.5)     ---QUIC (CID: abc123)--->  Server
                                    연결 유지!

8.4 스트림 멀티플렉싱과 HOL Blocking 해결

HTTP/2 over TCP:
  Stream A: [1] [2] [_] [4] ...  <- 패킷 3 손실
  Stream B: [1] [2] [3] [4] ...  <- B도 블로킹! (TCP가 순서 보장)
  Stream C: [1] [2] [3] [4] ...  <- C도 블로킹!
  
  TCP 재전송 완료까지 모든 스트림 대기

HTTP/3 over QUIC:
  Stream A: [1] [2] [_] [4] ...  <- 패킷 3 손실 (A만 대기)
  Stream B: [1] [2] [3] [4] ...  <- 정상 진행!
  Stream C: [1] [2] [3] [4] ...  <- 정상 진행!
  
  각 스트림은 독립적으로 재전송 처리

9. DNS 심화

9.1 DNS 해석 과정

사용자가 www.example.com 입력:

                     (1) 로컬 캐시 확인
Browser -------> OS Resolver -------> /etc/hosts
                     |
               (2) 캐시 미스
                     |
                     v
             Recursive Resolver (ISP/8.8.8.8)
                     |
            (3) 루트 서버에 질의
                     |
                     v
              Root Server (.)
              "com은 이 NS에 물어봐"
                     |
            (4) TLD 서버에 질의
                     |
                     v
            .com TLD Server
            "example.com은 이 NS에 물어봐"
                     |
            (5) 권한 서버에 질의
                     |
                     v
         example.com Authoritative NS
         "www.example.com = 93.184.216.34"
                     |
            (6) 결과 캐싱 및 반환
                     |
                     v
                  Browser

9.2 DNS 레코드 타입

# A 레코드 (IPv4)
dig A example.com
# example.com.    300    IN    A    93.184.216.34

# AAAA 레코드 (IPv6)
dig AAAA example.com

# CNAME (별칭)
dig CNAME www.example.com
# www.example.com. 300 IN CNAME example.com.

# MX (메일)
dig MX example.com

# NS (네임서버)
dig NS example.com

# TXT (텍스트 - SPF, DKIM 등)
dig TXT example.com

# 전체 해석 경로 추적
dig +trace www.example.com

9.3 DNSSEC

DNSSEC은 DNS 응답의 무결성을 보장합니다.

서명 체인:

Root Zone (.)
  |-- KSK (Key Signing Key) signs ZSK
  |-- ZSK (Zone Signing Key) signs records
  |-- DS record: .com의 KSK 해시
  |
  v
.com TLD
  |-- KSK, ZSK
  |-- DS record: example.com의 KSK 해시
  |
  v
example.com
  |-- KSK, ZSK
  |-- RRSIG: 각 레코드의 디지털 서명
  |-- A record + RRSIG(A)
# DNSSEC 검증
dig +dnssec example.com

# 서명 상태 확인
dig +sigchase +trusted-key=./trusted-key.key example.com

9.4 DoH / DoT

전통 DNS:     평문 UDP 53포트 (도청 가능)

DoT (DNS over TLS):
  - TLS로 암호화된 DNS
  - TCP 853번 포트
  - 시스템 레벨 설정 가능

DoH (DNS over HTTPS):
  - HTTPS로 캡슐화된 DNS
  - TCP 443포트 (일반 HTTPS와 구별 불가)
  - 브라우저 레벨 설정 (Firefox, Chrome)
# DoH로 DNS 쿼리 (curl)
curl -H "accept: application/dns-json" \
  "https://cloudflare-dns.com/dns-query?name=example.com&type=A"

10. TLS 1.3 심화

10.1 TLS 1.2 vs TLS 1.3

+----------------+------------------+------------------+
| 항목            | TLS 1.2          | TLS 1.3          |
+----------------+------------------+------------------+
| 핸드셰이크      | 2-RTT            | 1-RTT            |
| 재연결          | 1-RTT            | 0-RTT            |
| 키 교환         | RSA, DH, ECDH   | ECDHE, X25519|
| 암호화          | CBC, RC4 등 포함  | AEAD (AES-GCM, |
|                |                  | ChaCha20-Poly)   |
| 정적 RSA       | 가능 (PFS 없음)   | 제거 (PFS 필수)   |
| 압축           | 가능 (CRIME 취약)  | 제거              |
+----------------+------------------+------------------+

10.2 TLS 1.3 핸드셰이크

Client                              Server
  |                                    |
  |--- ClientHello ------------------>|
  |    + supported_versions           |
  |    + key_share (ECDHE 공개키)     |
  |    + signature_algorithms         |
  |    + psk_key_exchange_modes       |
  |                                    |
  |<-- ServerHello --------------------|
  |    + key_share (서버 ECDHE 공개키) |
  |                                    |
  |    [이후 모든 통신 암호화]          |
  |                                    |
  |<-- EncryptedExtensions ------------|
  |<-- Certificate --------------------|
  |<-- CertificateVerify --------------|
  |<-- Finished -----------------------|
  |                                    |
  |--- Finished ---------------------->|
  |                                    |
  |=== 1-RTT 핸드셰이크 완료 =========|

10.3 Certificate Transparency

인증서 발급 과정:

1. 도메인 소유자가 CA에 인증서 요청
2. CA가 인증서 발급
3. CACT 로그에 인증서 등록 (투명성!)
4. CT 로그가 SCT (Signed Certificate Timestamp) 반환
5. 서버가 TLS 핸드셰이크에 SCT 포함

CT 로그 모니터링:
- 누군가 내 도메인의 인증서를 무단 발급하면 감지 가능
- crt.sh에서 특정 도메인의 모든 인증서 조회 가능
# 서버의 TLS 인증서 확인
openssl s_client -connect example.com:443 -servername example.com \
  | openssl x509 -noout -text

# TLS 1.3 연결 테스트
openssl s_client -connect example.com:443 -tls1_3

# 인증서 체인 확인
openssl s_client -connect example.com:443 -showcerts

11. 네트워크 디버깅

11.1 tcpdump

# 특정 호스트와 포트 필터
tcpdump -i eth0 host 10.0.0.1 and port 80

# TCP 플래그 필터 (SYN 패킷만)
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn) != 0'

# 3-way 핸드셰이크 캡처
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' -c 100

# HTTP 요청/응답 내용 보기
tcpdump -i eth0 -A -s 0 'tcp port 80'

# pcap 파일로 저장 (Wireshark에서 분석)
tcpdump -i eth0 -w capture.pcap -c 10000

# DNS 쿼리 캡처
tcpdump -i eth0 udp port 53

# 재전송 패킷 확인
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn) != 0' | grep "retransmit"

11.2 ss (Socket Statistics)

# TCP 연결 상태 요약
ss -s

# LISTEN 상태 소켓 (서버 포트)
ss -tlnp

# ESTABLISHED 연결
ss -tnp state established

# TIME_WAIT 소켓 수
ss -s | grep TIME-WAIT

# 특정 포트의 연결 정보
ss -tnp dst :443

# 소켓 버퍼 크기 확인
ss -tnm

# 혼잡 제어 정보
ss -ti dst :80
# 출력 예: cubic wscale:7,7 rto:204 rtt:1.5/0.5
#          cwnd:10 ssthresh:7 send 77.0Mbps

11.3 traceroute / mtr

# 경로 추적
traceroute example.com

# TCP traceroute (방화벽 우회)
traceroute -T -p 443 example.com

# mtr (traceroute + ping 결합, 실시간 모니터링)
mtr --report-wide example.com

# 출력 예:
# HOST                    Loss%  Snt   Last   Avg  Best  Wrst StDev
# 1. gateway.local          0.0%  10    1.2   1.3   0.9   2.1   0.4
# 2. isp-router.net         0.0%  10    5.3   5.1   4.8   5.9   0.3
# 3. core-router.isp.net    0.0%  10   12.1  11.8  11.2  13.0   0.5
# 4. cdn-edge.example.com   0.0%  10   15.2  15.0  14.5  16.1   0.5

11.4 curl 디버깅

# 상세 연결 정보
curl -v https://example.com

# 타이밍 상세 정보
curl -w @- -o /dev/null -s https://example.com <<'EOF'
    time_namelookup:  %{time_namelookup}s\n
       time_connect:  %{time_connect}s\n
    time_appconnect:  %{time_appconnect}s\n
   time_pretransfer:  %{time_pretransfer}s\n
      time_redirect:  %{time_redirect}s\n
 time_starttransfer:  %{time_starttransfer}s\n
                     ----------\n
         time_total:  %{time_total}s\n
EOF

# HTTP/2 강제
curl --http2 -v https://example.com

# HTTP/3 (curl 7.88+ with nghttp3)
curl --http3 -v https://example.com

# TLS 핸드셰이크 정보
curl -v --tlsv1.3 https://example.com 2>&1 | grep -E "SSL|TLS"

12. 성능 튜닝

12.1 Nagle 알고리즘과 TCP_NODELAY

// Nagle 알고리즘: 작은 패킷을 모아서 전송 (대역폭 효율)
// 문제: 지연이 발생 (특히 실시간 애플리케이션)

// TCP_NODELAY로 Nagle 비활성화
int flag = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
           &flag, sizeof(flag));

// 언제 TCP_NODELAY를 사용하는가:
// - 실시간 게임 (프레임 단위 데이터)
// - 대화형 프로토콜 (SSH, telnet)
// - 작은 메시지를 빈번히 전송하는 경우
// - Redis, Memcached 같은 요청-응답 패턴

// 언제 Nagle을 유지하는가:
// - 대용량 파일 전송
// - 스트리밍 (이미 큰 패킷)
// - 대역폭이 제한적인 환경

12.2 소켓 버퍼 튜닝

# 현재 TCP 버퍼 설정 확인
sysctl net.ipv4.tcp_rmem  # 수신 버퍼 (min, default, max)
sysctl net.ipv4.tcp_wmem  # 송신 버퍼 (min, default, max)

# 고대역폭 환경 (10Gbps+) 튜닝
sysctl -w net.ipv4.tcp_rmem="4096 1048576 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 1048576 16777216"

# 전체 TCP 메모리 제한 (페이지 단위)
sysctl -w net.ipv4.tcp_mem="786432 1048576 1572864"

# 수신 버퍼 자동 튜닝 활성화
sysctl -w net.ipv4.tcp_moderate_rcvbuf=1

12.3 커널 네트워크 파라미터

# === 연결 관리 ===
# SYN 백로그 크기 (DDoS 방어)
sysctl -w net.ipv4.tcp_max_syn_backlog=65535

# Accept 큐 크기
sysctl -w net.core.somaxconn=65535

# SYN 쿠키 (SYN Flood 방어)
sysctl -w net.ipv4.tcp_syncookies=1

# === 타임아웃 ===
# FIN-WAIT-2 타임아웃 (기본 60초)
sysctl -w net.ipv4.tcp_fin_timeout=15

# Keepalive 설정
sysctl -w net.ipv4.tcp_keepalive_time=600
sysctl -w net.ipv4.tcp_keepalive_intvl=60
sysctl -w net.ipv4.tcp_keepalive_probes=3

# === 파일 디스크립터 ===
# 시스템 전체 파일 디스크립터 제한
sysctl -w fs.file-max=2097152

# 프로세스별 제한
ulimit -n 1048576

13. 실전 퀴즈

퀴즈 1: TCP 상태 전이

서버가 클라이언트의 FIN을 받은 후 아직 보낼 데이터가 남아있다면, 서버는 어떤 TCP 상태에 있는가?

정답: CLOSE_WAIT

서버가 클라이언트의 FIN을 받으면 ACK를 보내고 CLOSE_WAIT 상태로 전이합니다. 이 상태에서 서버는 여전히 데이터를 전송할 수 있습니다. 남은 데이터를 모두 보낸 후 서버가 FIN을 보내면 LAST_ACK 상태로 전이합니다.

CLOSE_WAIT 소켓이 많이 쌓이면 애플리케이션이 close()를 제대로 호출하지 않는 것이므로 버그입니다.

# CLOSE_WAIT 소켓 확인
ss -tnp state close-wait

퀴즈 2: HTTP/2 vs HTTP/3 HOL Blocking

HTTP/2에서 하나의 TCP 패킷이 손실되면 같은 연결의 다른 스트림도 영향을 받는 이유는 무엇인가?

정답: TCP의 순서 보장 특성 때문

HTTP/2는 하나의 TCP 연결에서 여러 스트림을 멀티플렉싱합니다. TCP는 바이트 스트림의 순서를 보장하므로, 중간 패킷이 손실되면 뒤따르는 모든 바이트(다른 스트림의 데이터 포함)가 TCP 수신 버퍼에서 대기해야 합니다.

HTTP/3(QUIC)는 UDP 위에서 각 스트림을 독립적으로 관리하여 이 문제를 해결합니다. 스트림 A의 패킷 손실은 스트림 B, C에 영향을 주지 않습니다.

퀴즈 3: BBR vs Cubic

높은 패킷 손실률(예: 위성 링크 2% 손실)이 있는 환경에서 BBR이 Cubic보다 유리한 이유는?

정답: Cubic은 손실 기반(loss-based) 알고리즘으로, 패킷 손실을 혼잡의 신호로 해석하여 cwnd를 급격히 줄입니다. 2% 손실 환경에서는 실제 혼잡이 아님에도 지속적으로 cwnd가 감소합니다.

BBR은 모델 기반(model-based)으로, 실제 대역폭(BtlBw)과 최소 RTT(RTprop)를 측정하여 최적 전송 속도를 결정합니다. 패킷 손실 자체를 혼잡 신호로 사용하지 않으므로, 높은 손실률에서도 가용 대역폭을 효과적으로 활용합니다.

퀴즈 4: TLS 1.3 0-RTT 보안

TLS 1.3의 0-RTT 재연결이 리플레이 공격에 취약한 이유와 대응 방법은?

정답: 0-RTT 데이터는 이전 세션의 PSK로 암호화되지만, 서버의 ServerHello를 포함하지 않아 신선성(freshness)이 보장되지 않습니다. 공격자가 0-RTT 패킷을 캡처하여 재전송하면 서버가 동일한 요청을 다시 처리할 수 있습니다.

대응 방법:

  • 0-RTT에는 멱등성(idempotent) 요청만 허용 (GET, HEAD)
  • 서버 측에서 anti-replay 메커니즘 구현 (Client Hello 해시 저장)
  • 금융 거래 등 민감한 작업은 0-RTT 비활성화

퀴즈 5: epoll ET 모드

epoll Edge-Triggered 모드에서 데이터를 한 번에 다 읽지 않으면 어떤 문제가 발생하는가?

정답: Edge-Triggered 모드는 상태 "변경" 시에만 이벤트를 발생시킵니다. 읽기 가능 상태로 변경된 후 데이터를 일부만 읽고 epoll_wait로 돌아가면, 남은 데이터에 대한 이벤트가 다시 발생하지 않습니다.

따라서 ET 모드에서는 EAGAIN이 반환될 때까지 반복적으로 read()를 호출하여 모든 데이터를 읽어야 합니다. 이것이 ET 모드가 반드시 non-blocking 소켓과 함께 사용되어야 하는 이유입니다.

Level-Triggered 모드에서는 데이터가 남아있으면 계속 이벤트가 발생하므로 이 문제가 없습니다.


14. 참고 자료

  1. TCP/IP Illustrated, Volume 1 - W. Richard Stevens (TCP의 바이블)
  2. Unix Network Programming - W. Richard Stevens (소켓 프로그래밍의 정석)
  3. High Performance Browser Networking - Ilya Grigorik (무료 온라인: hpbn.co)
  4. QUIC RFC 9000 - https://datatracker.ietf.org/doc/html/rfc9000
  5. TLS 1.3 RFC 8446 - https://datatracker.ietf.org/doc/html/rfc8446
  6. HTTP/3 RFC 9114 - https://datatracker.ietf.org/doc/html/rfc9114
  7. BBR Congestion Control - Google Research, https://research.google/pubs/bbr-congestion-based-congestion-control/
  8. Linux epoll(7) man page - https://man7.org/linux/man-pages/man7/epoll.7.html
  9. io_uring documentation - https://kernel.dk/io_uring.pdf
  10. Brendan Gregg - Network Performance - https://www.brendangregg.com/networking.html
  11. Cloudflare Blog - HTTP/3 - https://blog.cloudflare.com/http3-the-past-present-and-future/
  12. DNS Flag Day - https://dnsflagday.net/
  13. Certificate Transparency - https://certificate.transparency.dev/
  14. HPACK RFC 7541 - https://datatracker.ietf.org/doc/html/rfc7541

TCP/IP & Network Programming Deep Dive 2025: Sockets, HTTP/3, QUIC, DNS, TLS 1.3

Table of Contents

1. Why Deep Network Programming Knowledge Matters

For modern software engineers, networking is no longer something the infrastructure team handles alone. Latency between microservices determines P99 response times, HTTP/3 adoption decides mobile user experience, and a single TLS misconfiguration can fail a security audit.

Core topics covered in this guide:

  • TCP internals and state machine
  • Congestion control algorithms (Cubic, BBR, BBRv2)
  • Socket programming and I/O multiplexing (epoll, kqueue, io_uring)
  • HTTP/1.1 to HTTP/2 to HTTP/3 (QUIC) evolution
  • DNS resolution and security (DNSSEC, DoH/DoT)
  • TLS 1.3 handshake and 0-RTT
  • Network debugging tools (tcpdump, Wireshark, ss)
  • Performance tuning (Nagle, TCP_NODELAY, kernel parameters)

2. TCP Protocol Deep Dive

2.1 3-Way Handshake

The core process for establishing a TCP connection:

Client                    Server
  |                         |
  |--- SYN (seq=x) ------->|   (1) Client requests connection
  |                         |
  |<-- SYN-ACK ------------|   (2) Server accepts + sends its own SYN
  |    (seq=y, ack=x+1)    |
  |                         |
  |--- ACK (ack=y+1) ----->|   (3) Client acknowledges server's SYN
  |                         |
  |==== Connection Open ====|

Why 3-way? Both sides must confirm each other's Initial Sequence Number (ISN). With only 2-way, the server cannot verify the client's ACK.

// Server socket creation example
int server_fd = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(8080);

bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));

// backlog: determines SYN queue + Accept queue size
listen(server_fd, SOMAXCONN);

// Dequeue a completed 3-way handshake from the accept queue
int client_fd = accept(server_fd, NULL, NULL);

2.2 4-Way Termination

Connection teardown happens independently in each direction:

Client                    Server
  |                         |
  |--- FIN (seq=u) ------->|   (1) Client: "I have no more data"
  |                         |
  |<-- ACK (ack=u+1) ------|   (2) Server: "Acknowledged" (can still send)
  |                         |
  |    ... server sends remaining data ...
  |                         |
  |<-- FIN (seq=w) --------|   (3) Server: "I have no more data either"
  |                         |
  |--- ACK (ack=w+1) ----->|   (4) Client: "Acknowledged"
  |                         |
  |=== TIME_WAIT (2MSL) ===|   Client enters TIME_WAIT state

2.3 TCP State Machine

                          CLOSED
                            |
                      (active OPEN)
                            |
                        SYN_SENT
                            |
                      (SYN-ACK received)
                            |
                       ESTABLISHED
                          /    \
                 (active CLOSE) (passive CLOSE)
                        /        \
                  FIN_WAIT_1    CLOSE_WAIT
                      |              |
                  FIN_WAIT_2    LAST_ACK
                      |              |
                  TIME_WAIT      CLOSED
                      |
                   CLOSED

2.4 TIME_WAIT and SO_REUSEADDR

TIME_WAIT lasts for 2MSL (Maximum Segment Lifetime, typically 60 seconds).

Why TIME_WAIT is needed:

  1. Prevents delayed packets from interfering with new connections
  2. Allows retransmission if the final ACK is lost
// Prevent "Address already in use" on server restart
int optval = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR,
           &optval, sizeof(optval));

// Linux also supports SO_REUSEPORT (load balancing)
setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT,
           &optval, sizeof(optval));

Handling excessive TIME_WAIT:

# Check TIME_WAIT socket count
ss -s | grep TIME-WAIT

# Kernel parameter tuning (caution: side effects possible)
# Enable net.ipv4.tcp_tw_reuse
sysctl -w net.ipv4.tcp_tw_reuse=1

# Maximum TIME_WAIT sockets
sysctl -w net.ipv4.tcp_max_tw_buckets=262144

3. TCP Congestion Control

3.1 Why Congestion Control Is Necessary

When all hosts send packets without limit during congestion, router buffers overflow causing packet loss, and retransmissions make congestion even worse -- a phenomenon called Congestion Collapse.

3.2 Core Algorithms

cwnd (Congestion Window)
  ^
  |                    * * * *
  |                  *         *   <-- Congestion Avoidance (linear increase)
  |                *             *
  |              *                 \
  |            *                    \  Packet loss detected
  |          *                       \
  |        *   <-- Slow Start         * (cwnd halved)
  |      *     (exponential growth)    *
  |    *                                *
  |  *                                   * ...
  |*                                      
  +-----------------------------------------> Time
       ssthresh

Slow Start: cwnd starts at 1 MSS, increases by 1 MSS per ACK (doubles every RTT)

Congestion Avoidance: When cwnd reaches ssthresh, linear increase of 1 MSS per RTT

Fast Retransmit: Upon receiving 3 duplicate ACKs, retransmit immediately without waiting for timeout

Fast Recovery: On packet loss, set cwnd to half of ssthresh instead of 1

3.3 Cubic vs BBR

Algorithm Comparison:

+----------+--------+-----------+-------------------------------+
| Algorithm | Type   | Detection | Characteristics               |
+----------+--------+-----------+-------------------------------+
| Reno     | Loss   | Pkt loss  | Basic AIMD                    |
| Cubic    | Loss   | Pkt loss  | Cubic function, Linux default |
| BBR      | Model  | RTT/BW    | Bandwidth-delay model, Google |
| BBRv2    | Model  | RTT/BW    | Improved BBR, better fairness |
+----------+--------+-----------+-------------------------------+

Cubic: Linux default congestion control. Window size follows a cubic function.

# Check current congestion control algorithm
sysctl net.ipv4.tcp_congestion_control
# Output: net.ipv4.tcp_congestion_control = cubic

# Switch to BBR
sysctl -w net.ipv4.tcp_congestion_control=bbr

# List available algorithms
sysctl net.ipv4.tcp_available_congestion_control

BBR (Bottleneck Bandwidth and RTT):

  • Measures actual bandwidth and RTT to determine optimal send rate (not relying on packet loss)
  • Far superior to Cubic in high-loss environments (satellite, wireless)
  • Downside: fairness issues with other flows
# Enable BBR (kernel 4.9+)
modprobe tcp_bbr
echo "tcp_bbr" >> /etc/modules-load.d/bbr.conf
sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr

4. UDP: When and Why

4.1 TCP vs UDP

+------------+----------------------------+----------------------------+
| Feature    | TCP                        | UDP                        |
+------------+----------------------------+----------------------------+
| Connection | Connection-oriented (3-way)| Connectionless              |
| Reliability| Guaranteed (retransmit)    | No guarantee               |
| Flow Ctrl  | Yes (sliding window)       | None                       |
| Congestion | Yes                        | None (app-level)           |
| Header     | 20-60 bytes                | 8 bytes                    |
| Latency    | Higher (handshake+retrans) | Lower                      |
| Use Cases  | HTTP, SSH, DB              | DNS, gaming, streaming     |
+------------+----------------------------+----------------------------+

4.2 UDP Use Cases

Gaming: Per-frame position data -- only the latest value matters. The next frame is more important than retransmitting the previous one.

# Simple UDP game server
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', 9999))

players = {}
while True:
    data, addr = sock.recvfrom(1024)
    # Update player position (loss is acceptable)
    players[addr] = parse_position(data)
    
    # Broadcast game state to all players
    state = serialize_game_state(players)
    for player_addr in players:
        sock.sendto(state, player_addr)

DNS: Single request-response pattern. TCP handshake overhead is unnecessary.

Real-time streaming: RTP/RTCP protocols run over UDP.


5. Socket Programming

5.1 Berkeley Socket API

// Complete TCP client example
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    // 1. Create socket
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        return 1;
    }
    
    // 2. Configure server address
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(80);
    inet_pton(AF_INET, "93.184.216.34", &server_addr.sin_addr);
    
    // 3. Connect (triggers 3-way handshake)
    if (connect(sock, (struct sockaddr *)&server_addr,
                sizeof(server_addr)) < 0) {
        perror("connect");
        return 1;
    }
    
    // 4. Send HTTP request
    const char *request = "GET / HTTP/1.1\r\n"
                          "Host: example.com\r\n"
                          "Connection: close\r\n\r\n";
    send(sock, request, strlen(request), 0);
    
    // 5. Receive response
    char buffer[4096];
    int bytes;
    while ((bytes = recv(sock, buffer, sizeof(buffer) - 1, 0)) > 0) {
        buffer[bytes] = '\0';
        printf("%s", buffer);
    }
    
    // 6. Close connection (triggers 4-way teardown)
    close(sock);
    return 0;
}

5.2 Blocking vs Non-Blocking

#include <fcntl.h>

// Set non-blocking mode
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);

// Non-blocking connect
int ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0 && errno == EINPROGRESS) {
    // Connection in progress - use poll/epoll to check completion
    struct pollfd pfd;
    pfd.fd = sock;
    pfd.events = POLLOUT;
    poll(&pfd, 1, 5000);  // 5 second timeout
    
    int error;
    socklen_t len = sizeof(error);
    getsockopt(sock, SOL_LEVEL, SO_ERROR, &error, &len);
    if (error == 0) {
        // Connection succeeded
    }
}

// Non-blocking recv
char buf[1024];
ssize_t n = recv(sock, buf, sizeof(buf), 0);
if (n < 0) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
        // No data available yet - try again later
    } else {
        // Actual error
    }
}

6. I/O Multiplexing

6.1 select, poll, epoll, kqueue, io_uring Comparison

+------------+--------+-----------+----------+----------------------------+
| Mechanism  | OS     | Complexity| FD Limit | Characteristics            |
+------------+--------+-----------+----------+----------------------------+
| select     | All    | O(n)      | 1024     | Oldest, most portable      |
| poll       | All    | O(n)      | None     | Improved select, still O(n)|
| epoll      | Linux  | O(1)      | None     | Event-driven, scalable     |
| kqueue     | BSD    | O(1)      | None     | macOS/FreeBSD, feature-rich|
| io_uring   | Linux  | O(1)      | None     | 5.1+, async I/O revolution |
+------------+--------+-----------+----------+----------------------------+

6.2 epoll Deep Dive

#include <sys/epoll.h>

#define MAX_EVENTS 1024

int epoll_fd = epoll_create1(0);

// Register server socket
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = server_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);

struct epoll_event events[MAX_EVENTS];

while (1) {
    // Wait for events (blocking)
    int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
    
    for (int i = 0; i < nfds; i++) {
        if (events[i].data.fd == server_fd) {
            // Accept new connection
            int client_fd = accept(server_fd, NULL, NULL);
            
            // Set non-blocking
            fcntl(client_fd, F_SETFL,
                  fcntl(client_fd, F_GETFL, 0) | O_NONBLOCK);
            
            // Register with Edge-Triggered mode
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = client_fd;
            epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
        } else {
            // Handle client data
            handle_client(events[i].data.fd);
        }
    }
}

Level-Triggered vs Edge-Triggered:

  • LT (default): Notifies continuously as long as data is available. Safe but may cause unnecessary syscalls.
  • ET: Notifies only on state changes. Better performance but requires reading all data at once.
// Reading all data in Edge-Triggered mode
void handle_client_et(int fd) {
    char buf[4096];
    while (1) {
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n < 0) {
            if (errno == EAGAIN) break;  // All data read
            perror("read");
            break;
        }
        if (n == 0) {
            // Connection closed
            close(fd);
            break;
        }
        process_data(buf, n);
    }
}

6.3 io_uring (Linux 5.1+)

#include <liburing.h>

struct io_uring ring;
io_uring_queue_init(256, &ring, 0);

// Submit async read request
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, client_fd, buf, sizeof(buf), 0);
io_uring_sqe_set_data(sqe, client_ctx);
io_uring_submit(&ring);

// Harvest completion events
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);

struct client_ctx *ctx = io_uring_cqe_get_data(cqe);
int bytes_read = cqe->res;
// Process...

io_uring_cqe_seen(&ring, cqe);

io_uring innovations:

  • Communicates with kernel without syscalls (shared ring buffer)
  • Batch submission/completion minimizes overhead
  • Supports file I/O, network I/O, and timers
  • Up to 2-3x throughput improvement over epoll

7. HTTP Protocol Evolution

7.1 HTTP/1.1

HTTP/1.1 Key Features:

1. Keep-Alive (Persistent Connections)
   - Multiple requests/responses over a single TCP connection
   - Connection: keep-alive (default)

2. Pipelining
   - Send multiple requests without waiting for responses
   - Rarely used due to HOL Blocking in practice

3. Chunked Transfer Encoding
   - Streaming responses without Content-Length

HTTP/1.1 Limitation -- Head-of-Line Blocking:

Client              Server
  |-- GET /a -------->|
  |                   |  (response a takes 3 seconds to generate)
  |-- GET /b -------->|
  |                   |  b is ready but waits for a to finish
  |<-- Response /a ---|
  |<-- Response /b ---|  (unnecessary delay)

In practice, domain sharding (6-8 TCP connections) was used as a workaround.

7.2 HTTP/2

HTTP/2 Key Features:

1. Multiplexing
   - Multiple streams over a single TCP connection
   - Each stream is independent (solves HOL Blocking... except at TCP level)

2. HPACK Header Compression
   - Static/dynamic tables eliminate header duplication
   - First request: "content-type: application/json" (full)
   - Subsequent: just the index number

3. Server Push
   - Push CSS/JS proactively when HTML is requested
   - Rarely used in practice due to cache issues

4. Binary Framing
   - Binary frames instead of text
   - Improved parsing efficiency
HTTP/2 Frame Structure:

+-----------------------------------------------+
| Length (24)                                    |
+---------------+-------------------------------+
| Type (8)      | Flags (8)                     |
+---------------+-------------------------------+
| Reserved (1)  | Stream Identifier (31)        |
+-----------------------------------------------+
| Frame Payload                                 |
+-----------------------------------------------+

Stream IDs:
  - Odd: client-initiated
  - Even: server-initiated (Server Push)
  - 0: control frames

7.3 HTTP/3 (QUIC)

HTTP Version Comparison:

+----------+----------+---------+---------+----------------------------+
| Version  | Transport| Encrypt | RTT     | HOL Blocking               |
+----------+----------+---------+---------+----------------------------+
| HTTP/1.1 | TCP      | Optional| 2-3 RTT | Yes (response-level)       |
| HTTP/2   | TCP      | De facto| 2-3 RTT | Yes (TCP-level)            |
| HTTP/3   | QUIC/UDP | Required| 1 RTT   | None (stream-independent)  |
|          |          |         | (0 RTT) |                            |
+----------+----------+---------+---------+----------------------------+

8. QUIC Protocol Deep Dive

8.1 QUIC Architecture

Traditional Stack:       QUIC Stack:

+----------+            +----------+
| HTTP/2   |            | HTTP/3   |
+----------+            +----------+
| TLS 1.2+ |            | QUIC     |  <- TLS 1.3 built-in
+----------+            +----------+
| TCP      |            | UDP      |
+----------+            +----------+
| IP       |            | IP       |
+----------+            +----------+

8.2 0-RTT Connection

First Connection (1-RTT):

Client                    Server
  |                         |
  |--- Initial (CHLO) ---->|   ClientHello + transport parameters
  |                         |
  |<-- Initial (SHLO) -----|   ServerHello + certificate + transport params
  |                         |
  |--- Handshake Done ----->|
  |                         |
  |=== Data Transfer ======|   Connected in just 1-RTT!

Reconnection (0-RTT):

Client                    Server
  |                         |
  |--- Initial + 0-RTT --->|   Data encrypted with previous session key
  |    data                |
  |                         |   Server processes data immediately
  |<-- Handshake ----------|
  |                         |
  |=== Instant Exchange ===|   0-RTT!

0-RTT security caveat: Vulnerable to replay attacks. Only idempotent requests should use 0-RTT.

8.3 Connection Migration

Switching from Wi-Fi to cellular:

TCP: New IP address -> new connection needed -> 3-way handshake + TLS again
QUIC: Connection ID based -> connection survives IP change!

Client (Wi-Fi: 192.168.1.10)  ---QUIC (CID: abc123)--->  Server
         |
    (Wi-Fi drops, switch to cellular)
         |
Client (Cell: 10.0.0.5)       ---QUIC (CID: abc123)--->  Server
                                    Connection maintained!

8.4 Stream Multiplexing and HOL Blocking Resolution

HTTP/2 over TCP:
  Stream A: [1] [2] [_] [4] ...  <- Packet 3 lost
  Stream B: [1] [2] [3] [4] ...  <- B also blocked! (TCP guarantees order)
  Stream C: [1] [2] [3] [4] ...  <- C also blocked!
  
  All streams wait until TCP retransmission completes

HTTP/3 over QUIC:
  Stream A: [1] [2] [_] [4] ...  <- Packet 3 lost (only A waits)
  Stream B: [1] [2] [3] [4] ...  <- Proceeds normally!
  Stream C: [1] [2] [3] [4] ...  <- Proceeds normally!
  
  Each stream handles retransmission independently

9. DNS Deep Dive

9.1 DNS Resolution Process

User enters www.example.com:

                     (1) Check local cache
Browser -------> OS Resolver -------> /etc/hosts
                     |
               (2) Cache miss
                     |
                     v
             Recursive Resolver (ISP/8.8.8.8)
                     |
            (3) Query root server
                     |
                     v
              Root Server (.)
              "Ask this NS for .com"
                     |
            (4) Query TLD server
                     |
                     v
            .com TLD Server
            "Ask this NS for example.com"
                     |
            (5) Query authoritative server
                     |
                     v
         example.com Authoritative NS
         "www.example.com = 93.184.216.34"
                     |
            (6) Cache result and return
                     |
                     v
                  Browser

9.2 DNS Record Types

# A record (IPv4)
dig A example.com
# example.com.    300    IN    A    93.184.216.34

# AAAA record (IPv6)
dig AAAA example.com

# CNAME (alias)
dig CNAME www.example.com
# www.example.com. 300 IN CNAME example.com.

# MX (mail)
dig MX example.com

# NS (nameserver)
dig NS example.com

# TXT (text - SPF, DKIM, etc.)
dig TXT example.com

# Trace full resolution path
dig +trace www.example.com

9.3 DNSSEC

DNSSEC ensures integrity of DNS responses.

Signature Chain:

Root Zone (.)
  |-- KSK (Key Signing Key) signs ZSK
  |-- ZSK (Zone Signing Key) signs records
  |-- DS record: hash of .com KSK
  |
  v
.com TLD
  |-- KSK, ZSK
  |-- DS record: hash of example.com KSK
  |
  v
example.com
  |-- KSK, ZSK
  |-- RRSIG: digital signature of each record
  |-- A record + RRSIG(A)
# Verify DNSSEC
dig +dnssec example.com

# Check signature status
dig +sigchase +trusted-key=./trusted-key.key example.com

9.4 DoH / DoT

Traditional DNS:    Plaintext UDP port 53 (eavesdropping possible)

DoT (DNS over TLS):
  - DNS encrypted via TLS
  - TCP port 853
  - System-level configuration

DoH (DNS over HTTPS):
  - DNS encapsulated in HTTPS
  - TCP port 443 (indistinguishable from normal HTTPS)
  - Browser-level configuration (Firefox, Chrome)
# DNS query via DoH (curl)
curl -H "accept: application/dns-json" \
  "https://cloudflare-dns.com/dns-query?name=example.com&type=A"

10. TLS 1.3 Deep Dive

10.1 TLS 1.2 vs TLS 1.3

+----------------+------------------+------------------+
| Feature        | TLS 1.2          | TLS 1.3          |
+----------------+------------------+------------------+
| Handshake      | 2-RTT            | 1-RTT            |
| Reconnection   | 1-RTT            | 0-RTT            |
| Key Exchange   | RSA, DH, ECDH   | ECDHE, X25519    |
| Encryption     | Includes CBC, RC4| AEAD only        |
|                |                  | (AES-GCM,        |
|                |                  | ChaCha20-Poly)   |
| Static RSA     | Possible (no PFS)| Removed (PFS     |
|                |                  | required)        |
| Compression    | Yes (CRIME vuln) | Removed           |
+----------------+------------------+------------------+

10.2 TLS 1.3 Handshake

Client                              Server
  |                                    |
  |--- ClientHello ------------------>|
  |    + supported_versions           |
  |    + key_share (ECDHE public key) |
  |    + signature_algorithms         |
  |    + psk_key_exchange_modes       |
  |                                    |
  |<-- ServerHello --------------------|
  |    + key_share (server ECDHE key) |
  |                                    |
  |    [All subsequent comms encrypted]|
  |                                    |
  |<-- EncryptedExtensions ------------|
  |<-- Certificate --------------------|
  |<-- CertificateVerify --------------|
  |<-- Finished -----------------------|
  |                                    |
  |--- Finished ---------------------->|
  |                                    |
  |=== 1-RTT Handshake Complete ======|

10.3 Certificate Transparency

Certificate Issuance Process:

1. Domain owner requests certificate from CA
2. CA issues certificate
3. CA logs certificate in CT log (transparency!)
4. CT log returns SCT (Signed Certificate Timestamp)
5. Server includes SCT in TLS handshake

CT Log Monitoring:
- Detects unauthorized certificate issuance for your domain
- Query all certificates for a domain at crt.sh
# Check server TLS certificate
openssl s_client -connect example.com:443 -servername example.com \
  | openssl x509 -noout -text

# Test TLS 1.3 connection
openssl s_client -connect example.com:443 -tls1_3

# View certificate chain
openssl s_client -connect example.com:443 -showcerts

11. Network Debugging

11.1 tcpdump

# Filter by host and port
tcpdump -i eth0 host 10.0.0.1 and port 80

# Filter by TCP flags (SYN packets only)
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn) != 0'

# Capture 3-way handshake
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' -c 100

# View HTTP request/response content
tcpdump -i eth0 -A -s 0 'tcp port 80'

# Save to pcap file (analyze in Wireshark)
tcpdump -i eth0 -w capture.pcap -c 10000

# Capture DNS queries
tcpdump -i eth0 udp port 53

# Check retransmission packets
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn) != 0' | grep "retransmit"

11.2 ss (Socket Statistics)

# TCP connection state summary
ss -s

# LISTEN state sockets (server ports)
ss -tlnp

# ESTABLISHED connections
ss -tnp state established

# TIME_WAIT socket count
ss -s | grep TIME-WAIT

# Connection info for a specific port
ss -tnp dst :443

# Check socket buffer sizes
ss -tnm

# Congestion control info
ss -ti dst :80
# Example output: cubic wscale:7,7 rto:204 rtt:1.5/0.5
#                 cwnd:10 ssthresh:7 send 77.0Mbps

11.3 traceroute / mtr

# Path tracing
traceroute example.com

# TCP traceroute (bypass firewalls)
traceroute -T -p 443 example.com

# mtr (traceroute + ping combined, real-time monitoring)
mtr --report-wide example.com

# Example output:
# HOST                    Loss%  Snt   Last   Avg  Best  Wrst StDev
# 1. gateway.local          0.0%  10    1.2   1.3   0.9   2.1   0.4
# 2. isp-router.net         0.0%  10    5.3   5.1   4.8   5.9   0.3
# 3. core-router.isp.net    0.0%  10   12.1  11.8  11.2  13.0   0.5
# 4. cdn-edge.example.com   0.0%  10   15.2  15.0  14.5  16.1   0.5

11.4 curl Debugging

# Verbose connection info
curl -v https://example.com

# Detailed timing information
curl -w @- -o /dev/null -s https://example.com <<'EOF'
    time_namelookup:  %{time_namelookup}s\n
       time_connect:  %{time_connect}s\n
    time_appconnect:  %{time_appconnect}s\n
   time_pretransfer:  %{time_pretransfer}s\n
      time_redirect:  %{time_redirect}s\n
 time_starttransfer:  %{time_starttransfer}s\n
                     ----------\n
         time_total:  %{time_total}s\n
EOF

# Force HTTP/2
curl --http2 -v https://example.com

# HTTP/3 (curl 7.88+ with nghttp3)
curl --http3 -v https://example.com

# TLS handshake info
curl -v --tlsv1.3 https://example.com 2>&1 | grep -E "SSL|TLS"

12. Performance Tuning

12.1 Nagle's Algorithm and TCP_NODELAY

// Nagle's algorithm: aggregates small packets for bandwidth efficiency
// Problem: introduces latency (especially for real-time applications)

// Disable Nagle with TCP_NODELAY
int flag = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
           &flag, sizeof(flag));

// When to use TCP_NODELAY:
// - Real-time gaming (per-frame data)
// - Interactive protocols (SSH, telnet)
// - Frequent small message sends
// - Request-response patterns (Redis, Memcached)

// When to keep Nagle:
// - Large file transfers
// - Streaming (already large packets)
// - Bandwidth-constrained environments

12.2 Socket Buffer Tuning

# Check current TCP buffer settings
sysctl net.ipv4.tcp_rmem  # Receive buffer (min, default, max)
sysctl net.ipv4.tcp_wmem  # Send buffer (min, default, max)

# Tuning for high-bandwidth environments (10Gbps+)
sysctl -w net.ipv4.tcp_rmem="4096 1048576 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 1048576 16777216"

# Total TCP memory limit (in pages)
sysctl -w net.ipv4.tcp_mem="786432 1048576 1572864"

# Enable receive buffer auto-tuning
sysctl -w net.ipv4.tcp_moderate_rcvbuf=1

12.3 Kernel Network Parameters

# === Connection Management ===
# SYN backlog size (DDoS defense)
sysctl -w net.ipv4.tcp_max_syn_backlog=65535

# Accept queue size
sysctl -w net.core.somaxconn=65535

# SYN cookies (SYN Flood defense)
sysctl -w net.ipv4.tcp_syncookies=1

# === Timeouts ===
# FIN-WAIT-2 timeout (default 60s)
sysctl -w net.ipv4.tcp_fin_timeout=15

# Keepalive settings
sysctl -w net.ipv4.tcp_keepalive_time=600
sysctl -w net.ipv4.tcp_keepalive_intvl=60
sysctl -w net.ipv4.tcp_keepalive_probes=3

# === File Descriptors ===
# System-wide file descriptor limit
sysctl -w fs.file-max=2097152

# Per-process limit
ulimit -n 1048576

13. Practice Quiz

Quiz 1: TCP State Transition

After a server receives a FIN from the client but still has data to send, what TCP state is the server in?

Answer: CLOSE_WAIT

When the server receives the client's FIN, it sends an ACK and transitions to CLOSE_WAIT. In this state, the server can still send data. After sending all remaining data and its own FIN, the server transitions to LAST_ACK.

If many CLOSE_WAIT sockets accumulate, it typically indicates the application is not properly calling close() -- this is a bug.

# Check CLOSE_WAIT sockets
ss -tnp state close-wait

Quiz 2: HTTP/2 vs HTTP/3 HOL Blocking

Why does a single lost TCP packet in HTTP/2 affect other streams on the same connection?

Answer: TCP's in-order delivery guarantee

HTTP/2 multiplexes multiple streams over a single TCP connection. Since TCP guarantees byte-stream ordering, when an intermediate packet is lost, all subsequent bytes (including data for other streams) must wait in the TCP receive buffer.

HTTP/3 (QUIC) solves this by managing each stream independently over UDP. A packet loss in Stream A does not affect Streams B or C.

Quiz 3: BBR vs Cubic

Why does BBR outperform Cubic in high packet loss environments (e.g., 2% loss on satellite links)?

Answer: Cubic is a loss-based algorithm that interprets packet loss as a signal of congestion, aggressively reducing cwnd. In a 2% loss environment, cwnd is constantly decreased even when there is no actual congestion.

BBR is model-based, measuring actual bandwidth (BtlBw) and minimum RTT (RTprop) to determine optimal send rate. It does not use packet loss as a congestion signal, so it effectively utilizes available bandwidth even with high loss rates.

Quiz 4: TLS 1.3 0-RTT Security

Why is TLS 1.3 0-RTT reconnection vulnerable to replay attacks, and how can this be mitigated?

Answer: 0-RTT data is encrypted with the previous session's PSK but does not include the server's ServerHello, so freshness cannot be guaranteed. An attacker who captures 0-RTT packets can replay them, causing the server to process the same request again.

Mitigations:

  • Only allow idempotent requests (GET, HEAD) via 0-RTT
  • Implement server-side anti-replay mechanisms (store Client Hello hashes)
  • Disable 0-RTT for sensitive operations like financial transactions

Quiz 5: epoll ET Mode

What happens if you don't read all available data in epoll Edge-Triggered mode?

Answer: Edge-Triggered mode only fires events on state changes. After a readability change event, if you only read partial data and return to epoll_wait, no new event is generated for the remaining data.

Therefore, in ET mode, you must repeatedly call read() until EAGAIN is returned to consume all available data. This is why ET mode must always be used with non-blocking sockets.

Level-Triggered mode does not have this problem because it keeps generating events as long as data remains in the buffer.


14. References

  1. TCP/IP Illustrated, Volume 1 - W. Richard Stevens (The TCP Bible)
  2. Unix Network Programming - W. Richard Stevens (Socket programming classic)
  3. High Performance Browser Networking - Ilya Grigorik (Free online: hpbn.co)
  4. QUIC RFC 9000 - https://datatracker.ietf.org/doc/html/rfc9000
  5. TLS 1.3 RFC 8446 - https://datatracker.ietf.org/doc/html/rfc8446
  6. HTTP/3 RFC 9114 - https://datatracker.ietf.org/doc/html/rfc9114
  7. BBR Congestion Control - Google Research, https://research.google/pubs/bbr-congestion-based-congestion-control/
  8. Linux epoll(7) man page - https://man7.org/linux/man-pages/man7/epoll.7.html
  9. io_uring documentation - https://kernel.dk/io_uring.pdf
  10. Brendan Gregg - Network Performance - https://www.brendangregg.com/networking.html
  11. Cloudflare Blog - HTTP/3 - https://blog.cloudflare.com/http3-the-past-present-and-future/
  12. DNS Flag Day - https://dnsflagday.net/
  13. Certificate Transparency - https://certificate.transparency.dev/
  14. HPACK RFC 7541 - https://datatracker.ietf.org/doc/html/rfc7541