OSI 7계층 완벽 이해 (커널 & 애플리케이션 관점)
목차
OSI 7계층 개요
왜 7계층으로 나눴을까?
네트워크 통신을 7개의 계층으로 분리한 이유:
- 관심사의 분리: 각 계층은 독립적인 책임을 가짐
- 유지보수 용이성: 특정 계층만 수정 가능
- 표준화: 서로 다른 시스템 간 호환성 보장
- 추상화: 하위 계층의 복잡성을 상위 계층으로부터 숨김
계층 구조 한눈에 보기
┌─────────────────────────────────────────────────────┐
│ Application Layer (L7) - HTTP, FTP, DNS, SSH │ User Space
│ Presentation Layer (L6) - SSL/TLS, JPEG, MPEG │ 애플리케이션
│ Session Layer (L5) - NetBIOS, RPC │
├─────────────────────────────────────────────────────┤
│ Transport Layer (L4) - TCP, UDP │ Kernel Space
│ Network Layer (L3) - IP, ICMP, Routing │ (일부 User Space)
│ Data Link Layer (L2) - Ethernet, MAC, Switch │ Kernel Space
│ Physical Layer (L1) - Cable, Hub, Voltage │ Hardware
└─────────────────────────────────────────────────────┘각 계층 상세 분석
Layer 7: Application Layer (응용 계층)
역할
사용자가 직접 상호작용하는 네트워크 서비스 제공
주요 프로토콜
- HTTP/HTTPS: 웹 브라우징
- FTP: 파일 전송
- SMTP/POP3/IMAP: 이메일
- DNS: 도메인 이름 해석
- SSH: 보안 원격 접속
- Telnet: 원격 접속 (비보안)
커널과의 관계
Application Layer는 완전히 User Space에서 동작합니다.
// 예: HTTP 클라이언트 (User Space)
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int sock = socket(AF_INET, SOCK_STREAM, 0); // System call
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(80);
inet_pton(AF_INET, "93.184.216.34", &server.sin_addr);
connect(sock, (struct sockaddr*)&server, sizeof(server)); // System call
// HTTP 요청 (L7)
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); // System call → Kernel로 이동
char buffer[4096];
recv(sock, buffer, sizeof(buffer), 0); // System call
printf("%s\n", buffer);
close(sock);
return 0;
}핵심 포인트
- User Space에서만 동작
socket(),send(),recv()같은 System Call을 통해 커널과 통신- 애플리케이션 개발자가 직접 다루는 영역
- 데이터 포맷, 프로토콜 규칙을 정의
Layer 6: Presentation Layer (표현 계층)
역할
데이터의 인코딩, 암호화, 압축을 담당
주요 기능
- 데이터 변환: ASCII ↔ EBCDIC
- 암호화/복호화: SSL/TLS
- 압축/해제: JPEG, MPEG, GIF
- 직렬화: JSON, XML, Protocol Buffers
커널과의 관계
Presentation Layer도 주로 User Space에서 동작하지만, SSL/TLS는 커널 모듈로도 구현될 수 있습니다.
// OpenSSL을 사용한 HTTPS (L6 + L7)
#include <openssl/ssl.h>
#include <openssl/err.h>
int main() {
SSL_library_init();
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
int sock = socket(AF_INET, SOCK_STREAM, 0);
// ... connect ...
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, sock);
SSL_connect(ssl); // TLS Handshake (L6)
// 이제 데이터는 암호화되어 전송됨
SSL_write(ssl, "GET / HTTP/1.1\r\n...", ...); // L7 데이터를 L6에서 암호화
char buffer[4096];
SSL_read(ssl, buffer, sizeof(buffer)); // L6에서 복호화 → L7로 전달
SSL_shutdown(ssl);
SSL_free(ssl);
close(sock);
return 0;
}Linux 커널의 TLS 구현
Linux 커널 4.13부터 Kernel TLS (kTLS) 지원:
// kTLS 사용 예제
#include <linux/tls.h>
int sock = socket(AF_INET, SOCK_STREAM, 0);
// ... connect ...
// TLS를 커널에서 처리하도록 설정
setsockopt(sock, SOL_TCP, TCP_ULP, "tls", sizeof("tls"));
struct tls12_crypto_info_aes_gcm_128 crypto_info;
// ... crypto_info 설정 ...
setsockopt(sock, SOL_TLS, TLS_TX, &crypto_info, sizeof(crypto_info));
// 이제 send()로 보내는 데이터는 커널에서 자동으로 암호화됨
send(sock, "plaintext data", 14, 0); // 커널이 암호화핵심 포인트
- 대부분 User Space (OpenSSL, zlib 등)
- 일부 커널 구현 (kTLS, 압축 알고리즘)
- 애플리케이션 데이터를 네트워크 전송에 적합한 형태로 변환
Layer 5: Session Layer (세션 계층)
역할
통신 세션의 생성, 유지, 종료를 관리
주요 기능
- 세션 설정/해제
- 동기화: 체크포인트 설정
- 대화 제어: Half-duplex, Full-duplex
- 연결 복구
주요 프로토콜
- NetBIOS: Windows 네트워크
- RPC (Remote Procedure Call)
- PPTP (Point-to-Point Tunneling Protocol)
- PAP/CHAP: 인증 프로토콜
커널과의 관계
Session Layer는 주로 User Space에 구현되지만, 일부 기능은 커널의 도움을 받습니다.
// RPC 예제 (Sun RPC / gRPC 같은 프레임워크)
// L5: 세션 관리
// L7: 실제 RPC 호출
// 서버 측
struct sockaddr_in server;
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
bind(listen_sock, ...);
listen(listen_sock, 5);
// 세션 관리
while (1) {
int client_sock = accept(listen_sock, ...); // 새 세션 생성 (L5)
// 세션 상태 유지
pid_t pid = fork(); // 프로세스별 세션 관리
if (pid == 0) {
// 세션 동안 데이터 교환 (L7)
while (recv(client_sock, buffer, ...) > 0) {
// 요청 처리
send(client_sock, response, ...);
}
close(client_sock); // 세션 종료 (L5)
exit(0);
}
}Linux 커널의 세션 지원
// SO_KEEPALIVE: TCP 세션 유지
int sock = socket(AF_INET, SOCK_STREAM, 0);
int keepalive = 1;
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));
// 커널이 주기적으로 keepalive 패킷을 보내서 세션 유지
int keepidle = 60; // 60초 idle 후 keepalive 시작
int keepintvl = 10; // 10초마다 재전송
int keepcnt = 5; // 5번 실패하면 연결 끊김
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));핵심 포인트
- User Space 애플리케이션이 주도
- 커널의 SO_KEEPALIVE, 타임아웃 등의 기능 활용
- 연결 지향적 통신의 수명 주기 관리
Layer 4: Transport Layer (전송 계층)
역할
종단 간(End-to-End) 신뢰성 있는 데이터 전송
주요 프로토콜
TCP (Transmission Control Protocol)
- 연결 지향
- 신뢰성 보장 (재전송, 순서 보장)
- 흐름 제어, 혼잡 제어
UDP (User Datagram Protocol)
- 비연결형
- 빠른 전송
- 신뢰성 보장 없음
커널과의 관계
Transport Layer는 거의 완전히 Linux 커널에 구현되어 있습니다.
TCP의 커널 구현
User Space:
┌──────────────┐
│ Application │
│ send() │
└──────┬───────┘
│ System Call
▼
Kernel Space:
┌──────────────┐
│ sys_send() │ ← System Call Handler
└──────┬───────┘
▼
┌──────────────┐
│ TCP Layer │ ← net/ipv4/tcp.c
│ - Segmentation
│ - Sequence #
│ - Checksum
│ - Retrans
└──────┬───────┘
▼
┌──────────────┐
│ IP Layer │ ← L3
└──────────────┘커널 코드 위치 (Linux)
# TCP 구현
net/ipv4/tcp.c # TCP 메인 로직
net/ipv4/tcp_input.c # TCP 입력 처리
net/ipv4/tcp_output.c # TCP 출력 처리
net/ipv4/tcp_timer.c # TCP 타이머 (재전송 등)
net/ipv4/tcp_congestion.c # 혼잡 제어 (Cubic, BBR, Reno...)
# UDP 구현
net/ipv4/udp.c # UDP 메인 로직TCP 연결 설정 (3-Way Handshake)
// 클라이언트 측
int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, ...); // 이 시점에 커널이 3-way handshake 수행
// 커널 내부 동작 (pseudo-code)
// 1. SYN 전송
tcp_send_syn(sock);
// 2. SYN-ACK 수신 대기
tcp_wait_syn_ack(sock);
// 3. ACK 전송
tcp_send_ack(sock);
// 이제 ESTABLISHED 상태
sock->state = TCP_ESTABLISHED;TCP 상태 다이어그램과 커널
CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
↓ ↑
SYN_RCVD (data transfer)# 현재 TCP 연결 상태 확인
ss -tan
# 또는
cat /proc/net/tcp각 상태는 커널의 struct sock 구조체의 state 필드에 저장됩니다:
// include/net/tcp_states.h
enum {
TCP_ESTABLISHED = 1,
TCP_SYN_SENT,
TCP_SYN_RECV,
TCP_FIN_WAIT1,
TCP_FIN_WAIT2,
TCP_TIME_WAIT,
TCP_CLOSE,
// ...
};TCP 재전송 메커니즘
// User Space에서는 그냥 send()
send(sock, data, len, 0);
// 커널 내부에서는...
// 1. 데이터를 TCP 세그먼트로 분할
// 2. Sequence Number 할당
// 3. 전송 후 재전송 타이머 설정
// net/ipv4/tcp_output.c
void tcp_write_xmit() {
// 세그먼트 전송
tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);
// 재전송 타이머 설정
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
}
// ACK를 받지 못하면 타이머 만료 시 재전송
// net/ipv4/tcp_timer.c
void tcp_retransmit_timer() {
tcp_retransmit_skb(sk, skb); // 재전송
}Socket Buffer (sk_buff)
커널은 네트워크 데이터를 sk_buff 구조체로 관리합니다:
// include/linux/skbuff.h
struct sk_buff {
struct sk_buff *next;
struct sk_buff *prev;
struct sock *sk; // 소유한 소켓
unsigned char *head; // 버퍼 시작
unsigned char *data; // 데이터 시작
unsigned char *tail; // 데이터 끝
unsigned char *end; // 버퍼 끝
unsigned int len; // 데이터 길이
// 각 계층의 헤더 포인터
struct tcphdr *th; // L4: TCP 헤더
struct iphdr *iph; // L3: IP 헤더
struct ethhdr *eth; // L2: Ethernet 헤더
};데이터 전송 흐름:
send(sock, "Hello", 5, 0)
↓
[User Space Buffer]
↓ copy_from_user()
[Kernel Space: sk_buff]
┌─────────────────────┐
│ head │
│ data → "Hello" │
│ tail │
│ end │
└─────────────────────┘
↓
TCP adds header:
┌──────┬──────────┐
│ TCP │ "Hello" │
│ hdr │ │
└──────┴──────────┘
↓ L3핵심 포인트
- 완전히 커널 구현 (
net/ipv4/tcp.c,net/ipv4/udp.c) - User Space는 System Call만 사용 (
send(),recv(),connect()등) - 재전송, 흐름 제어, 혼잡 제어 모두 커널이 자동 처리
/proc/net/tcp,/proc/net/udp로 상태 확인 가능
Layer 3: Network Layer (네트워크 계층)
역할
서로 다른 네트워크 간 패킷 라우팅 및 전달
주요 프로토콜
- IP (Internet Protocol): IPv4, IPv6
- ICMP (Internet Control Message Protocol): ping, traceroute
- ARP (Address Resolution Protocol): IP → MAC 주소 변환
- Routing Protocols: OSPF, BGP, RIP
커널과의 관계
Network Layer도 거의 완전히 커널에 구현되어 있습니다.
IP 패킷 구조
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+커널의 IP Layer 구현
# IP 관련 커널 코드
net/ipv4/ip_input.c # IP 패킷 입력 처리
net/ipv4/ip_output.c # IP 패킷 출력 처리
net/ipv4/route.c # 라우팅 테이블
net/ipv4/ip_forward.c # IP 포워딩
net/ipv4/icmp.c # ICMP 프로토콜
net/ipv4/arp.c # ARP 프로토콜IP 라우팅
// 커널의 라우팅 결정 (pseudo-code)
// net/ipv4/route.c
struct rtable *ip_route_output(struct net *net, __be32 daddr) {
// 1. 라우팅 테이블 검색
struct fib_result res;
fib_lookup(net, daddr, &res);
// 2. 다음 홉(hop) 결정
if (res.type == RTN_LOCAL) {
// 로컬 주소 → 루프백
return local_route;
} else if (res.type == RTN_UNICAST) {
// 외부 주소 → 게이트웨이로 전달
return unicast_route;
}
// 3. 라우팅 캐시에 저장
rt_cache_add(daddr, route);
return route;
}라우팅 테이블 확인:
# 라우팅 테이블 보기
ip route show
# 또는
route -n
# 또는
cat /proc/net/routeUser Space에서 Raw IP 패킷 만들기
일반적으로 커널이 처리하지만, Raw Socket으로 직접 IP 패킷을 만들 수 있습니다:
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <string.h>
#include <arpa/inet.h>
int main() {
// Raw socket 생성 (root 권한 필요)
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
char packet[4096];
memset(packet, 0, 4096);
// IP 헤더 직접 작성
struct iphdr *iph = (struct iphdr *)packet;
iph->version = 4;
iph->ihl = 5;
iph->tos = 0;
iph->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
iph->id = htons(54321);
iph->frag_off = 0;
iph->ttl = 64;
iph->protocol = IPPROTO_TCP;
iph->saddr = inet_addr("192.168.1.100");
iph->daddr = inet_addr("192.168.1.1");
// TCP 헤더도 직접 작성
struct tcphdr *tcph = (struct tcphdr *)(packet + sizeof(struct iphdr));
tcph->source = htons(12345);
tcph->dest = htons(80);
// ... TCP 헤더 필드 설정 ...
struct sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = iph->daddr;
// Raw 패킷 전송 → 커널이 L2로 전달
sendto(sock, packet, iph->tot_len, 0,
(struct sockaddr *)&dest, sizeof(dest));
return 0;
}ICMP: ping의 동작
ping 8.8.8.8내부 동작:
// 1. User Space에서 ICMP socket 생성
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
// 2. ICMP Echo Request 패킷 생성
struct icmphdr {
uint8_t type; // 8 (Echo Request)
uint8_t code; // 0
uint16_t checksum;
uint16_t id;
uint16_t sequence;
};
// 3. 전송
sendto(sock, icmp_packet, ...);
// 커널 내부: net/ipv4/icmp.c
// - IP 헤더 추가
// - 라우팅 결정
// - L2로 전달
// 4. 응답 수신
recvfrom(sock, buffer, ...);
// ICMP Echo Reply (type=0) 수신ARP (Address Resolution Protocol)
IP 주소를 MAC 주소로 변환:
# ARP 테이블 확인
arp -a
# 또는
ip neigh show
# 또는
cat /proc/net/arp커널의 ARP 처리 (net/ipv4/arp.c):
// IP → MAC 주소 변환
// 1. ARP 캐시 검색
struct neighbour *neigh = neigh_lookup(&arp_tbl, &daddr, dev);
if (neigh == NULL) {
// 2. 캐시에 없으면 ARP Request 브로드캐스트
arp_send(ARPOP_REQUEST, ETH_P_ARP, target_ip, dev,
src_ip, NULL, dev->dev_addr, NULL);
// 3. ARP Reply 대기
// ...
// 4. 캐시에 저장
neigh_update(...);
}
// 5. MAC 주소 반환
return neigh->ha; // Hardware Address (MAC)핵심 포인트
- 완전히 커널에 구현 (
net/ipv4/) - 라우팅 테이블은 커널이 관리, User Space에서
ip route로 설정 - ARP 테이블도 커널이 관리,
/proc/net/arp로 확인 - Raw Socket으로 User Space에서도 IP 패킷 직접 생성 가능 (root 필요)
Layer 2: Data Link Layer (데이터 링크 계층)
역할
같은 네트워크(LAN) 내에서 물리적 주소(MAC)를 사용한 프레임 전송
주요 프로토콜 및 장비
- Ethernet: 가장 널리 사용되는 LAN 기술
- MAC (Media Access Control)
- Switch: MAC 주소 기반 프레임 스위칭
- PPP (Point-to-Point Protocol)
- VLAN (Virtual LAN)
커널과의 관계
Data Link Layer는 주로 커널의 네트워크 드라이버에 구현되어 있습니다.
Ethernet Frame 구조
┌──────────┬──────────┬──────┬─────────┬─────┬────┐
│ Preamble │ Dst MAC │ Src │ Type/ │Data │FCS │
│ (7B) │ (6B) │ MAC │ Length │ │(4B)│
│ │ │ (6B) │ (2B) │ │ │
└──────────┴──────────┴──────┴─────────┴─────┴────┘
L1 L2 L2 L2 L3+ L2커널의 Data Link Layer
# 네트워크 드라이버 위치
drivers/net/ethernet/ # Ethernet 드라이버들
drivers/net/wireless/ # 무선랜 드라이버들
# Data Link 관련 커널 코드
net/ethernet/eth.c # Ethernet 처리
net/core/dev.c # 네트워크 장치 관리
net/8021q/ # VLAN 구현Network Device 구조체
// include/linux/netdevice.h
struct net_device {
char name[IFNAMSIZ]; // "eth0", "wlan0" 등
unsigned char dev_addr[MAX_ADDR_LEN]; // MAC 주소
unsigned char broadcast[MAX_ADDR_LEN]; // 브로드캐스트 주소
unsigned int mtu; // Maximum Transmission Unit
struct net_device_ops *netdev_ops; // 장치 operations
// 통계
struct net_device_stats stats;
// ...
};
// 장치 operations
struct net_device_ops {
int (*ndo_open)(struct net_device *dev);
int (*ndo_stop)(struct net_device *dev);
netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb, struct net_device *dev);
// ...
};패킷 전송 흐름 (L3 → L2)
// L3 (IP Layer)에서 패킷 전송
// net/ipv4/ip_output.c
int ip_output(struct sk_buff *skb) {
// 라우팅 결정 완료
// dst_output()을 통해 L2로 전달
return dst_output(skb); // → ip_finish_output()
}
int ip_finish_output(struct sk_buff *skb) {
// ARP로 MAC 주소 확인
struct neighbour *neigh = dst_neigh_output(skb);
// L2 헤더 추가
return neigh->output(neigh, skb); // → dev_queue_xmit()
}
// net/core/dev.c
int dev_queue_xmit(struct sk_buff *skb) {
struct net_device *dev = skb->dev;
// Ethernet 헤더 추가
dev_hard_header(skb, dev, ETH_P_IP,
dst_hw_addr, src_hw_addr, skb->len);
// 네트워크 드라이버로 전달
return dev->netdev_ops->ndo_start_xmit(skb, dev);
}
// drivers/net/ethernet/intel/e1000/e1000_main.c (예시)
static netdev_tx_t e1000_xmit_frame(struct sk_buff *skb,
struct net_device *netdev) {
// DMA를 사용해 NIC에 데이터 전송
e1000_tx_map(adapter, skb);
// 하드웨어에 전송 명령
writel(tx_ring->next_to_use, adapter->hw.hw_addr + E1000_TDT);
return NETDEV_TX_OK;
}User Space에서 L2 다루기
// Raw Ethernet Socket (AF_PACKET)
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <string.h>
int main() {
// L2 Raw Socket 생성
int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
// 인터페이스 바인딩
struct sockaddr_ll addr;
memset(&addr, 0, sizeof(addr));
addr.sll_family = AF_PACKET;
addr.sll_ifindex = if_nametoindex("eth0");
addr.sll_protocol = htons(ETH_P_ALL);
bind(sock, (struct sockaddr*)&addr, sizeof(addr));
// Ethernet 프레임 직접 생성
unsigned char frame[1518];
struct ethhdr *eth = (struct ethhdr *)frame;
// Destination MAC (브로드캐스트)
memset(eth->h_dest, 0xff, ETH_ALEN);
// Source MAC
unsigned char src_mac[6] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
memcpy(eth->h_source, src_mac, ETH_ALEN);
// EtherType (IPv4)
eth->h_proto = htons(ETH_P_IP);
// Payload (IP 패킷 등)
// ...
// 프레임 전송
sendto(sock, frame, frame_len, 0,
(struct sockaddr*)&addr, sizeof(addr));
return 0;
}MAC 주소 확인
# 네트워크 인터페이스 정보
ip link show
# eth0의 MAC 주소 확인
cat /sys/class/net/eth0/address
# 또는
ifconfig eth0 | grep etherVLAN (Virtual LAN)
# VLAN 인터페이스 생성
ip link add link eth0 name eth0.100 type vlan id 100
# VLAN 태그가 있는 패킷은 커널이 자동 처리
# net/8021q/vlan_dev.cVLAN 태그 구조:
┌──────────┬──────────┬──────┬─────────┬─────────┬─────┬────┐
│ Dst MAC │ Src MAC │ 0x81 │ VLAN │Type/Len │Data │FCS │
│ (6B) │ (6B) │ 00 │ Tag(2B) │ (2B) │ │(4B)│
└──────────┴──────────┴──────┴─────────┴─────────┴─────┴────┘
↑ ↑
802.1Q 12bit VLAN ID핵심 포인트
- 커널의 네트워크 드라이버가 담당 (
drivers/net/,net/ethernet/) - MAC 주소는 NIC(Network Interface Card)에 저장, 커널이 읽어서 사용
- AF_PACKET 소켓으로 User Space에서도 접근 가능
- Switch는 커널 외부의 하드웨어 장치
Layer 1: Physical Layer (물리 계층)
역할
비트를 전기 신호, 광 신호, 무선 신호로 변환하여 물리적 매체를 통해 전송
주요 요소
- 전송 매체: 동축 케이블, 광섬유, UTP(Unshielded Twisted Pair)
- 신호 변조: Manchester Encoding, 4B/5B Encoding
- 장비: Hub, Repeater, NIC(Network Interface Card)
커널과의 관계
Physical Layer는 거의 완전히 하드웨어가 담당하며, 커널은 드라이버를 통해 하드웨어를 제어합니다.
NIC (Network Interface Card)
NIC의 역할:
- 디지털 데이터 → 전기/광 신호 변환
- MAC 주소 저장
- CRC(Cyclic Redundancy Check) 계산 (FCS)
- DMA(Direct Memory Access)를 통해 메모리와 직접 통신
커널 드라이버의 역할
// drivers/net/ethernet/intel/e1000/e1000_main.c (예시)
// NIC 초기화
static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent) {
struct net_device *netdev;
struct e1000_adapter *adapter;
// 1. PCI 장치 활성화
pci_enable_device(pdev);
// 2. MMIO(Memory-Mapped I/O) 영역 매핑
adapter->hw.hw_addr = ioremap(pci_resource_start(pdev, 0), ...);
// 3. 인터럽트 핸들러 등록
request_irq(pdev->irq, e1000_intr, IRQF_SHARED, netdev->name, netdev);
// 4. DMA 버퍼 할당
adapter->tx_ring = dma_alloc_coherent(&pdev->dev, ...);
adapter->rx_ring = dma_alloc_coherent(&pdev->dev, ...);
// 5. 네트워크 장치 등록
register_netdev(netdev);
return 0;
}
// 패킷 전송
static netdev_tx_t e1000_xmit_frame(struct sk_buff *skb,
struct net_device *netdev) {
// 1. DMA를 위한 물리 주소 매핑
dma_addr_t dma = dma_map_single(&pdev->dev, skb->data, skb->len, DMA_TO_DEVICE);
// 2. TX Descriptor에 주소 설정
tx_ring->buffer_info[i].dma = dma;
tx_ring->desc[i].buffer_addr = cpu_to_le64(dma);
tx_ring->desc[i].length = cpu_to_le16(skb->len);
// 3. NIC에 전송 명령 (MMIO write)
writel(tx_ring->next_to_use, hw->hw_addr + E1000_TDT);
// → NIC가 DMA로 메모리에서 데이터를 읽어서 물리적으로 전송
return NETDEV_TX_OK;
}
// 패킷 수신 (인터럽트 핸들러)
static irqreturn_t e1000_intr(int irq, void *data) {
// NIC가 패킷을 DMA로 메모리에 쓰고 인터럽트 발생
// 1. Interrupt Cause Register 확인
uint32_t icr = readl(hw->hw_addr + E1000_ICR);
if (icr & E1000_ICR_RXT0) { // 수신 인터럽트
// 2. NAPI 스케줄링 (나중에 처리)
napi_schedule(&adapter->napi);
}
return IRQ_HANDLED;
}
// NAPI poll (실제 수신 처리)
static int e1000_clean_rx_irq(struct e1000_adapter *adapter) {
struct e1000_rx_ring *rx_ring = adapter->rx_ring;
// RX Descriptor 순회
while (rx_ring->next_to_clean != rx_ring->next_to_use) {
struct e1000_rx_desc *rx_desc = &rx_ring->desc[i];
// 1. DMA 완료 확인
if (!(rx_desc->status & E1000_RXD_STAT_DD))
break; // 아직 도착 안 함
// 2. sk_buff 생성
struct sk_buff *skb = netdev_alloc_skb(netdev, length);
memcpy(skb->data, rx_buffer, length);
// 3. Ethernet 프레임 타입 확인
skb->protocol = eth_type_trans(skb, netdev);
// 4. 상위 계층(L3)으로 전달
netif_receive_skb(skb); // → IP Layer
}
return work_done;
}DMA (Direct Memory Access)
NIC는 CPU의 개입 없이 메모리와 직접 통신합니다:
CPU가 직접 복사 (비효율적):
CPU → NIC → Wire
DMA 사용 (효율적):
CPU는 명령만 내림
Memory ←→ NIC (DMA) → Wire물리적 신호 변환
이 부분은 완전히 NIC 하드웨어가 담당:
Digital (커널) → Analog (NIC 하드웨어) → Wire
예: Ethernet (100BASE-TX)
- 디지털 비트: 0, 1, 0, 1, 1, 0, ...
- MLT-3 인코딩: +1V, 0V, -1V, 0V, +1V, ...
- 물리적 전송: 동축 케이블 / UTP 케이블User Space에서 확인 가능한 정보
# NIC 정보
lspci | grep -i ethernet
ethtool eth0
# Link 상태 (물리적 연결)
cat /sys/class/net/eth0/carrier # 1: 연결됨, 0: 연결 안 됨
cat /sys/class/net/eth0/speed # 1000 (Mbps)
cat /sys/class/net/eth0/duplex # full / half
# 드라이버 정보
ethtool -i eth0
# 하드웨어 통계
ethtool -S eth0핵심 포인트
- 거의 완전히 하드웨어가 담당
- 커널 드라이버는 하드웨어 제어 인터페이스 제공
- DMA를 통해 효율적인 데이터 전송
- User Space에서는
ethtool,/sys/class/net/으로 상태 확인 가능
Linux 커널과의 연관성
전체 데이터 흐름 요약
┌─────────────────────────────────────────────────────────────┐
│ User Space │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Application (L7) │ │
│ │ - HTTP client/server, FTP, DNS, etc. │ │
│ │ - write()/send() system call │ │
│ └───────────────────┬──────────────────────────────────┘ │
│ │ copy_from_user() │
└──────────────────────┼──────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Kernel Space │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Socket Layer (sys_send, sys_recv) │ │
│ └───────────────────┬──────────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Transport Layer (L4) - net/ipv4/tcp.c │ │
│ │ - TCP: segmentation, seq#, checksum, retransmit │ │
│ │ - sk_buff 관리 │ │
│ └───────────────────┬──────────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Network Layer (L3) - net/ipv4/ip_output.c │ │
│ │ - IP 헤더 추가, 라우팅, fragmentation │ │
│ │ - ARP (IP → MAC) │ │
│ └───────────────────┬──────────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Data Link Layer (L2) - net/core/dev.c │ │
│ │ - Ethernet 헤더 추가 │ │
│ │ - dev_queue_xmit() │ │
│ └───────────────────┬──────────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Network Driver - drivers/net/ethernet/ │ │
│ │ - DMA 설정, NIC에 전송 명령 │ │
│ └───────────────────┬──────────────────────────────────┘ │
└──────────────────────┼──────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Hardware (L1) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ NIC (Network Interface Card) │ │
│ │ - DMA로 메모리에서 데이터 읽기 │ │
│ │ - 디지털 → 전기/광 신호 변환 │ │
│ │ - 물리적 전송 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘계층별 커널 코드 위치
| 계층 | 위치 | 주요 파일 |
|---|---|---|
| L7 | User Space | (애플리케이션) |
| L6 | User Space / Kernel | net/tls/ (kTLS) |
| L5 | User Space | (애플리케이션) |
| L4 | Kernel | net/ipv4/tcp.c, net/ipv4/udp.c |
| L3 | Kernel | net/ipv4/ip_output.c, net/ipv4/route.c |
| L2 | Kernel | net/core/dev.c, drivers/net/ethernet/ |
| L1 | Hardware + Driver | drivers/net/ethernet/*/ |
실제 데이터 흐름
예제: HTTP GET 요청
// User Space Application
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main() {
// 1. Socket 생성
int sock = socket(AF_INET, SOCK_STREAM, 0);
// 2. 서버 주소 설정
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(80);
inet_pton(AF_INET, "93.184.216.34", &server.sin_addr); // example.com
// 3. 연결 (3-way handshake)
connect(sock, (struct sockaddr*)&server, sizeof(server));
// 4. HTTP 요청 전송
char *request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
send(sock, request, strlen(request), 0);
// 5. 응답 수신
char buffer[4096];
recv(sock, buffer, sizeof(buffer), 0);
// 6. 연결 종료
close(sock);
return 0;
}내부 흐름 추적
1. socket() 시스템 콜
// User Space
int sock = socket(AF_INET, SOCK_STREAM, 0);
↓
// Kernel: net/socket.c
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) {
sock = sock_create(family, type, protocol); // struct socket 생성
fd = sock_map_fd(sock); // 파일 디스크립터 할당
return fd;
}2. connect() 시스템 콜 (3-Way Handshake)
// User Space
connect(sock, &server, sizeof(server));
↓
// Kernel: net/socket.c
SYSCALL_DEFINE3(connect, int, fd, struct sockaddr*, addr, int, addrlen) {
sock = sockfd_lookup(fd);
sock->ops->connect(sock, addr, addrlen); // → inet_stream_connect
}
↓
// net/ipv4/af_inet.c
int inet_stream_connect(struct socket *sock, struct sockaddr *addr) {
tcp_v4_connect(sk, addr);
}
↓
// net/ipv4/tcp_ipv4.c
int tcp_v4_connect(struct sock *sk, struct sockaddr *addr) {
// 1. 라우팅 결정
rt = ip_route_connect(daddr);
// 2. 로컬 포트 할당
inet_hash_connect(sk);
// 3. TCP 상태 변경
tcp_set_state(sk, TCP_SYN_SENT);
// 4. SYN 패킷 전송
tcp_connect(sk); // → tcp_transmit_skb()
}SYN 패킷 구조:
┌────────────────────────────────────────────────────┐
│ Ethernet Header (L2) │
│ Dst MAC: Router MAC │
│ Src MAC: My MAC │
│ Type: 0x0800 (IPv4) │
├────────────────────────────────────────────────────┤
│ IP Header (L3) │
│ Src IP: 192.168.1.100 │
│ Dst IP: 93.184.216.34 │
│ Protocol: 6 (TCP) │
├────────────────────────────────────────────────────┤
│ TCP Header (L4) │
│ Src Port: 54321 (random) │
│ Dst Port: 80 │
│ Flags: SYN │
│ Seq: 1000 (random) │
├────────────────────────────────────────────────────┤
│ (No Data) │
└────────────────────────────────────────────────────┘3. send() 시스템 콜
// User Space
send(sock, "GET / HTTP/1.1...", 26, 0);
↓
// Kernel: net/socket.c
SYSCALL_DEFINE4(send, int, fd, void*, buff, size_t, len, unsigned, flags) {
sock = sockfd_lookup(fd);
// User Space → Kernel Space 복사
copy_from_user(kernel_buffer, buff, len);
sock->ops->sendmsg(sock, msg, len); // → tcp_sendmsg
}
↓
// net/ipv4/tcp.c
int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) {
// 1. sk_buff 할당
skb = sk_stream_alloc_skb(sk, size, sk->sk_allocation);
// 2. 데이터 복사
skb_copy_to_page(skb, page, offset, copy);
// 3. 전송 큐에 추가
tcp_push(sk, flags, mss_now, TCP_NAGLE_PUSH);
}
↓
// net/ipv4/tcp_output.c
void tcp_write_xmit(struct sock *sk) {
// TCP 세그먼트 생성
tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);
}
↓
// net/ipv4/ip_output.c
int ip_queue_xmit(struct sock *sk, struct sk_buff *skb) {
// IP 헤더 추가
iph = ip_hdr(skb);
iph->saddr = inet->inet_saddr;
iph->daddr = inet->inet_daddr;
iph->protocol = sk->sk_protocol;
// 라우팅
ip_local_out(skb);
}
↓
// net/core/dev.c
int dev_queue_xmit(struct sk_buff *skb) {
// Ethernet 헤더 추가
dev_hard_header(skb, dev, ETH_P_IP, ...);
// NIC 드라이버 호출
dev->netdev_ops->ndo_start_xmit(skb, dev);
}
↓
// drivers/net/ethernet/intel/e1000/e1000_main.c
static netdev_tx_t e1000_xmit_frame(struct sk_buff *skb, struct net_device *dev) {
// DMA 매핑
dma = dma_map_single(&pdev->dev, skb->data, skb->len, DMA_TO_DEVICE);
// TX Descriptor 설정
tx_desc->buffer_addr = cpu_to_le64(dma);
// NIC에 전송 명령
writel(i, hw->hw_addr + E1000_TDT);
// → NIC가 DMA로 데이터를 읽어서 물리적으로 전송
}전송되는 패킷:
┌────────────────────────────────────────────────────┐
│ Ethernet Header (14 bytes) │
├────────────────────────────────────────────────────┤
│ IP Header (20 bytes) │
├────────────────────────────────────────────────────┤
│ TCP Header (20 bytes) │
│ Flags: PSH, ACK │
│ Seq: 1001 │
│ Ack: 1001 │
├────────────────────────────────────────────────────┤
│ HTTP Data (26 bytes) │
│ "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" │
└────────────────────────────────────────────────────┘
Total: 80 bytes4. 패킷 수신 (recv)
Physical Layer (L1)
↓ (전기 신호 도착)
NIC (Network Interface Card)
↓ (DMA로 메모리에 쓰기)
↓ (인터럽트 발생)
// drivers/net/ethernet/intel/e1000/e1000_main.c
static irqreturn_t e1000_intr(int irq, void *data) {
napi_schedule(&adapter->napi); // 나중에 처리 (효율성)
}
↓
static int e1000_clean_rx_irq(struct e1000_adapter *adapter) {
// 1. RX Descriptor에서 패킷 읽기
skb = netdev_alloc_skb(netdev, length);
memcpy(skb->data, rx_buffer, length);
// 2. Ethernet 타입 확인
skb->protocol = eth_type_trans(skb, netdev); // L2 처리
// 3. 상위 계층으로 전달
netif_receive_skb(skb);
}
↓
// net/core/dev.c
int netif_receive_skb(struct sk_buff *skb) {
// Ethernet 타입에 따라 분기
// 0x0800 (IPv4) → ip_rcv()
// 0x0806 (ARP) → arp_rcv()
deliver_skb(skb, pt_prev, orig_dev);
}
↓
// net/ipv4/ip_input.c
int ip_rcv(struct sk_buff *skb) {
// IP 헤더 검증
iph = ip_hdr(skb);
// 라우팅 확인 (local인지 forward할지)
ip_rcv_finish(skb); // → ip_local_deliver()
}
↓
int ip_local_deliver(struct sk_buff *skb) {
// IP fragmentation 재조립
// Protocol 필드에 따라 분기
// 6 (TCP) → tcp_v4_rcv()
// 17 (UDP) → udp_rcv()
ipprot->handler(skb);
}
↓
// net/ipv4/tcp_ipv4.c
int tcp_v4_rcv(struct sk_buff *skb) {
th = tcp_hdr(skb);
// 포트로 소켓 찾기
sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
// TCP 상태 머신 처리
tcp_v4_do_rcv(sk, skb);
}
↓
// net/ipv4/tcp_input.c
void tcp_data_queue(struct sock *sk, struct sk_buff *skb) {
// 1. Sequence number 확인
// 2. 순서대로 정렬
// 3. 수신 버퍼에 추가
skb_queue_tail(&sk->sk_receive_queue, skb);
// 4. User Space를 깨움 (대기 중이라면)
sk->sk_data_ready(sk);
}
↓
// User Space
recv(sock, buffer, 4096, 0);
↓
// Kernel: net/socket.c
SYSCALL_DEFINE4(recv, int, fd, void*, ubuf, size_t, size, unsigned, flags) {
sock = sockfd_lookup(fd);
// 수신 큐에서 데이터 가져오기
skb = skb_dequeue(&sk->sk_receive_queue);
// Kernel Space → User Space 복사
copy_to_user(ubuf, skb->data, skb->len);
return skb->len;
}실습 예제
1. 계층별 패킷 캡처 (tcpdump)
# 모든 계층의 패킷 캡처
sudo tcpdump -i eth0 -XX
# L2: Ethernet 헤더 포함
sudo tcpdump -i eth0 -e
# L3: IP 주소 필터
sudo tcpdump -i eth0 host 8.8.8.8
# L4: TCP 포트 필터
sudo tcpdump -i eth0 tcp port 80
# L7: HTTP 요청만
sudo tcpdump -i eth0 -A 'tcp port 80 and (tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420)' # "GET "2. 계층별 네트워크 도구
# L1: 물리 계층
ethtool eth0 # NIC 정보
mii-tool eth0 # Link 상태
# L2: 데이터 링크 계층
ip link show # 인터페이스 목록
arp -a # ARP 테이블
bridge fdb show # MAC 주소 테이블
# L3: 네트워크 계층
ip route show # 라우팅 테이블
ping 8.8.8.8 # ICMP Echo
traceroute google.com # 경로 추적
ip neigh show # ARP 캐시
# L4: 전송 계층
ss -tan # TCP 연결 상태
ss -uan # UDP 소켓
netstat -s # 프로토콜 통계
# L7: 응용 계층
curl http://example.com # HTTP
dig google.com # DNS
telnet example.com 80 # TCP 연결 테스트3. 커널 네트워크 통계
# 전체 네트워크 통계
cat /proc/net/dev # 인터페이스별 통계
cat /proc/net/snmp # SNMP 통계 (IP, TCP, UDP, ICMP)
# TCP 상태별 연결 수
cat /proc/net/tcp | awk '{print $4}' | sort | uniq -c
# 소켓 통계
ss -s
# 커널 네트워크 파라미터
sysctl -a | grep net.4. 실시간 패킷 분석 (C 프로그램)
// packet_sniffer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
void print_ethernet_header(unsigned char *buffer) {
struct ethhdr *eth = (struct ethhdr *)buffer;
printf("\n=== Ethernet Header (L2) ===\n");
printf("Dst MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
eth->h_dest[0], eth->h_dest[1], eth->h_dest[2],
eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]);
printf("Src MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
eth->h_source[0], eth->h_source[1], eth->h_source[2],
eth->h_source[3], eth->h_source[4], eth->h_source[5]);
printf("Protocol: 0x%04x\n", ntohs(eth->h_proto));
}
void print_ip_header(unsigned char *buffer) {
struct iphdr *iph = (struct iphdr *)(buffer + sizeof(struct ethhdr));
printf("\n=== IP Header (L3) ===\n");
struct sockaddr_in source, dest;
source.sin_addr.s_addr = iph->saddr;
dest.sin_addr.s_addr = iph->daddr;
printf("Version: %d\n", iph->version);
printf("Header Length: %d bytes\n", iph->ihl * 4);
printf("TTL: %d\n", iph->ttl);
printf("Protocol: %d\n", iph->protocol);
printf("Source IP: %s\n", inet_ntoa(source.sin_addr));
printf("Dest IP: %s\n", inet_ntoa(dest.sin_addr));
}
void print_tcp_header(unsigned char *buffer) {
struct iphdr *iph = (struct iphdr *)(buffer + sizeof(struct ethhdr));
struct tcphdr *tcph = (struct tcphdr *)(buffer + sizeof(struct ethhdr) + iph->ihl * 4);
printf("\n=== TCP Header (L4) ===\n");
printf("Source Port: %u\n", ntohs(tcph->source));
printf("Dest Port: %u\n", ntohs(tcph->dest));
printf("Sequence: %u\n", ntohl(tcph->seq));
printf("Ack: %u\n", ntohl(tcph->ack_seq));
printf("Flags: ");
if (tcph->syn) printf("SYN ");
if (tcph->ack) printf("ACK ");
if (tcph->psh) printf("PSH ");
if (tcph->fin) printf("FIN ");
printf("\n");
}
int main() {
int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0) {
perror("socket");
return 1;
}
unsigned char buffer[65536];
printf("Listening on all interfaces...\n");
while (1) {
ssize_t size = recvfrom(sock, buffer, sizeof(buffer), 0, NULL, NULL);
if (size < 0) {
perror("recvfrom");
continue;
}
// L2: Ethernet
print_ethernet_header(buffer);
struct ethhdr *eth = (struct ethhdr *)buffer;
if (ntohs(eth->h_proto) == ETH_P_IP) {
// L3: IP
print_ip_header(buffer);
struct iphdr *iph = (struct iphdr *)(buffer + sizeof(struct ethhdr));
if (iph->protocol == IPPROTO_TCP) {
// L4: TCP
print_tcp_header(buffer);
// L7: Application Data
int header_size = sizeof(struct ethhdr) + iph->ihl * 4 +
((struct tcphdr *)(buffer + sizeof(struct ethhdr) + iph->ihl * 4))->doff * 4;
int data_size = size - header_size;
if (data_size > 0) {
printf("\n=== Application Data (L7) ===\n");
printf("Size: %d bytes\n", data_size);
printf("Data: ");
for (int i = 0; i < (data_size < 50 ? data_size : 50); i++) {
printf("%c", buffer[header_size + i]);
}
printf("\n");
}
}
}
printf("\n========================================\n");
}
return 0;
}컴파일 및 실행:
gcc -o packet_sniffer packet_sniffer.c
sudo ./packet_sniffer정리
각 계층의 역할 요약
| 계층 | 역할 | 주요 구현 위치 | 데이터 단위 |
|---|---|---|---|
| L7: Application | 사용자 서비스 | User Space | Data |
| L6: Presentation | 데이터 변환, 암호화 | User Space (일부 Kernel) | Data |
| L5: Session | 세션 관리 | User Space | Data |
| L4: Transport | End-to-End 전송 | Kernel | Segment |
| L3: Network | 라우팅, 주소 지정 | Kernel | Packet |
| L2: Data Link | MAC 주소 기반 전송 | Kernel + Driver | Frame |
| L1: Physical | 물리적 신호 전송 | Hardware | Bit |
User Space vs Kernel Space
User Space:
- L7, L6, L5 (애플리케이션)
- System Call로 커널과 통신
Kernel Space:
- L4: TCP/UDP 구현
- L3: IP, 라우팅
- L2: Ethernet, 드라이버
Hardware:
- L1: NIC (물리적 전송)학습 로드맵
- 기초: TCP/IP 4계층 모델 이해
- 중급: 커널 코드 읽기 (
net/ipv4/tcp.c) - 고급: 커널 모듈 작성, 패킷 필터링 (Netfilter/iptables)
- 전문가: 커널 네트워크 스택 최적화, DPDK (커널 우회)
다음 강의에서는 TCP의 내부 동작 (3-Way Handshake, 재전송, 흐름 제어, 혼잡 제어) 을 심층적으로 다루겠습니다.