Skip to content

OSI 7계층 완벽 이해 (커널 & 애플리케이션 관점)

목차

  1. OSI 7계층 개요
  2. 각 계층 상세 분석
  3. Linux 커널과의 연관성
  4. 실제 데이터 흐름
  5. 실습 예제

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에서 동작합니다.

c
// 예: 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는 커널 모듈로도 구현될 수 있습니다.

c
// 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) 지원:

c
// 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에 구현되지만, 일부 기능은 커널의 도움을 받습니다.

c
// 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 커널의 세션 지원

c
// 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)

bash
# 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)

c
// 클라이언트 측
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)
bash
# 현재 TCP 연결 상태 확인
ss -tan
# 또는
cat /proc/net/tcp

각 상태는 커널의 struct sock 구조체의 state 필드에 저장됩니다:

c
// 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 재전송 메커니즘

c
// 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 구조체로 관리합니다:

c
// 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 구현

bash
# 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 라우팅

c
// 커널의 라우팅 결정 (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;
}

라우팅 테이블 확인:

bash
# 라우팅 테이블 보기
ip route show
# 또는
route -n
# 또는
cat /proc/net/route

User Space에서 Raw IP 패킷 만들기

일반적으로 커널이 처리하지만, Raw Socket으로 직접 IP 패킷을 만들 수 있습니다:

c
#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의 동작

bash
ping 8.8.8.8

내부 동작:

c
// 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 주소로 변환:

bash
# ARP 테이블 확인
arp -a
# 또는
ip neigh show
# 또는
cat /proc/net/arp

커널의 ARP 처리 (net/ipv4/arp.c):

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 필요)

역할

같은 네트워크(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
bash
# 네트워크 드라이버 위치
drivers/net/ethernet/       # Ethernet 드라이버들
drivers/net/wireless/       # 무선랜 드라이버들

# Data Link 관련 커널 코드
net/ethernet/eth.c          # Ethernet 처리
net/core/dev.c              # 네트워크 장치 관리
net/8021q/                  # VLAN 구현

Network Device 구조체

c
// 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)

c
// 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 다루기

c
// 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 주소 확인

bash
# 네트워크 인터페이스 정보
ip link show

# eth0의 MAC 주소 확인
cat /sys/class/net/eth0/address

# 또는
ifconfig eth0 | grep ether

VLAN (Virtual LAN)

bash
# VLAN 인터페이스 생성
ip link add link eth0 name eth0.100 type vlan id 100

# VLAN 태그가 있는 패킷은 커널이 자동 처리
# net/8021q/vlan_dev.c

VLAN 태그 구조:

┌──────────┬──────────┬──────┬─────────┬─────────┬─────┬────┐
│ 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의 역할:

  1. 디지털 데이터 → 전기/광 신호 변환
  2. MAC 주소 저장
  3. CRC(Cyclic Redundancy Check) 계산 (FCS)
  4. DMA(Direct Memory Access)를 통해 메모리와 직접 통신

커널 드라이버의 역할

c
// 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에서 확인 가능한 정보

bash
# 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로 메모리에서 데이터 읽기                        │   │
│  │  - 디지털 → 전기/광 신호 변환                          │   │
│  │  - 물리적 전송                                         │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

계층별 커널 코드 위치

계층위치주요 파일
L7User Space(애플리케이션)
L6User Space / Kernelnet/tls/ (kTLS)
L5User Space(애플리케이션)
L4Kernelnet/ipv4/tcp.c, net/ipv4/udp.c
L3Kernelnet/ipv4/ip_output.c, net/ipv4/route.c
L2Kernelnet/core/dev.c, drivers/net/ethernet/
L1Hardware + Driverdrivers/net/ethernet/*/

실제 데이터 흐름

예제: HTTP GET 요청

c
// 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() 시스템 콜

c
// 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)

c
// 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() 시스템 콜

c
// 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 bytes

4. 패킷 수신 (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)

bash
# 모든 계층의 패킷 캡처
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. 계층별 네트워크 도구

bash
# 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. 커널 네트워크 통계

bash
# 전체 네트워크 통계
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 프로그램)

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;
}

컴파일 및 실행:

bash
gcc -o packet_sniffer packet_sniffer.c
sudo ./packet_sniffer

정리

각 계층의 역할 요약

계층역할주요 구현 위치데이터 단위
L7: Application사용자 서비스User SpaceData
L6: Presentation데이터 변환, 암호화User Space (일부 Kernel)Data
L5: Session세션 관리User SpaceData
L4: TransportEnd-to-End 전송KernelSegment
L3: Network라우팅, 주소 지정KernelPacket
L2: Data LinkMAC 주소 기반 전송Kernel + DriverFrame
L1: Physical물리적 신호 전송HardwareBit

User Space vs Kernel Space

User Space:
- L7, L6, L5 (애플리케이션)
- System Call로 커널과 통신

Kernel Space:
- L4: TCP/UDP 구현
- L3: IP, 라우팅
- L2: Ethernet, 드라이버

Hardware:
- L1: NIC (물리적 전송)

학습 로드맵

  1. 기초: TCP/IP 4계층 모델 이해
  2. 중급: 커널 코드 읽기 (net/ipv4/tcp.c)
  3. 고급: 커널 모듈 작성, 패킷 필터링 (Netfilter/iptables)
  4. 전문가: 커널 네트워크 스택 최적화, DPDK (커널 우회)

다음 강의에서는 TCP의 내부 동작 (3-Way Handshake, 재전송, 흐름 제어, 혼잡 제어) 을 심층적으로 다루겠습니다.