Skip to content

Golang으로 tcpdump 만들기 - 네트워크 패킷 분석 완벽 가이드 ​

목차 ​

  1. tcpdump란 무엇인가?
  2. 필요한 네트워크 개념
  3. Raw Socket 이해하기
  4. 단계별 구현
  5. 고급 기능 추가
  6. 실전 활용

tcpdump란 무엇인가? ​

정의 ​

tcpdump는 네트워크 인터페이스를 통과하는 모든 패킷을 캡처하고 분석하는 도구입니다.

주요 용도 ​

  • 네트워크 디버깅: 왜 연결이 안 되는지 확인
  • 보안 분석: 비정상적인 트래픽 탐지
  • 성능 분석: 패킷 손실, 지연 측정
  • 프로토콜 학습: 실제 패킷 구조 확인

동작 원리 ​

┌─────────────────────────────────────────────────────────┐
│                  Network Traffic Flow                   │
└─────────────────────────────────────────────────────────┘

  Internet
     │
     ▼
┌─────────┐
│   NIC   │ ← Physical Layer (L1)
└────┬────┘
     │ DMA → Memory
     ▼
┌─────────────────────────────────────────────────────────┐
│                   Kernel Space                          │
│                                                         │
│  ┌──────────────────────────────────────────────────┐   │
│  │  Network Driver                                  │   │
│  └────────────┬─────────────────────────────────────┘   │
│               │                                         │
│               ├──────────┬──────────────┐               │
│               │          │              │               │
│               ▼          ▼              ▼               │
│     ┌─────────────┐  ┌──────────┐  ┌─────────────┐     │
│     │   Normal    │  │   TAP    │  │  AF_PACKET  │     │
│     │   Path      │  │ (복사본)  │  │   Socket    │     │
│     │   (L2→L3    │  │          │  │             │     │
│     │   →L4→L7)   │  └────┬─────┘  └──────┬──────┘     │
│     └─────────────┘       │               │             │
│           │               │               │             │
└───────────┼───────────────┼───────────────┼─────────────┘
            │               │               │
            ▼               └───────────────┼───────────┐
     ┌─────────────┐                       │           │
     │   Normal    │                       │           │
     │Application  │                       │           │
     │  (Chrome,   │                       │           │
     │   ssh...)   │                       │           │
     └─────────────┘                       │           │
                                           ▼           │
                                    ┌──────────────┐   │
                                    │   tcpdump    │◄──┘
                                    │  (우리가     │
                                    │   만들 것)   │
                                    └──────────────┘

핵심: tcpdump는 패킷의 복사본을 받기 때문에 정상 트래픽을 방해하지 않습니다.


필요한 네트워크 개념 ​

1. Packet Structure (패킷 구조) ​

실제 네트워크에서 전송되는 데이터 형태:

┌────────────────────────────────────────────────────────┐
│                  Complete Packet                       │
├────────────────────────────────────────────────────────┤
│                                                        │
│  ┌──────────────────────────────────────────────┐     │
│  │ Ethernet Header (14 bytes)           [L2]    │     │
│  ├──────────────────────────────────────────────┤     │
│  │ Destination MAC (6 bytes)                    │     │
│  │ Source MAC      (6 bytes)                    │     │
│  │ EtherType       (2 bytes)  ex: 0x0800 (IPv4) │     │
│  └──────────────────────────────────────────────┘     │
│                                                        │
│  ┌──────────────────────────────────────────────┐     │
│  │ IP Header (20+ bytes)                [L3]    │     │
│  ├──────────────────────────────────────────────┤     │
│  │ Version, IHL, TOS                            │     │
│  │ Total Length                                 │     │
│  │ TTL, Protocol (6=TCP, 17=UDP, 1=ICMP)        │     │
│  │ Source IP      (4 bytes)                     │     │
│  │ Destination IP (4 bytes)                     │     │
│  └──────────────────────────────────────────────┘     │
│                                                        │
│  ┌──────────────────────────────────────────────┐     │
│  │ TCP/UDP Header (20+ bytes)           [L4]    │     │
│  ├──────────────────────────────────────────────┤     │
│  │ Source Port      (2 bytes)                   │     │
│  │ Destination Port (2 bytes)                   │     │
│  │ Sequence Number  (4 bytes)  - TCP only       │     │
│  │ Flags (SYN, ACK, FIN...)    - TCP only       │     │
│  └──────────────────────────────────────────────┘     │
│                                                        │
│  ┌──────────────────────────────────────────────┐     │
│  │ Application Data                     [L7]    │     │
│  ├──────────────────────────────────────────────┤     │
│  │ "GET / HTTP/1.1\r\n..."                      │     │
│  │ or any application data                      │     │
│  └──────────────────────────────────────────────┘     │
│                                                        │
└────────────────────────────────────────────────────────┘

2. Byte Order (Endianness) ​

네트워크는 Big Endian (Network Byte Order)을 사용합니다.

go
// Host Byte Order (CPU 의존적)
var port uint16 = 80

// Host → Network 변환
networkPort := htons(port)  // 80 → 0x0050 (Big Endian)

// Network → Host 변환
hostPort := ntohs(networkPort)

예시:

숫자 0x1234 (4660)

Little Endian (Intel x86):
  Memory: [34] [12]
           ↑    ↑
          낮은  높은
          주소  주소

Big Endian (Network):
  Memory: [12] [34]
           ↑    ↑
          낮은  높은
          주소  주소

3. MAC Address (Media Access Control) ​

6 bytes (48 bits) 의 하드웨어 주소:

00:1A:2B:3C:4D:5E
│  │  │  │  │  │
│  │  │  │  │  └─ NIC Specific
│  │  │  └─────── NIC Specific
│  │  └────────── NIC Specific
│  └───────────── Manufacturer
└──────────────── OUI (Organizationally Unique Identifier)

특수 MAC 주소:

  • FF:FF:FF:FF:FF:FF - Broadcast (모든 장치에게)
  • 01:00:5E:xx:xx:xx - Multicast (그룹)

4. IP Address ​

IPv4: 4 bytes (32 bits)

192.168.1.100
 │   │  │  │
 └───┴──┴──┴─ 각 옥텟(octet)은 0~255

Binary: 11000000.10101000.00000001.01100100
Hex:    C0      .A8      .01      .64

특수 IP 주소:

  • 127.0.0.1 - Loopback (자기 자신)
  • 0.0.0.0 - 모든 인터페이스
  • 255.255.255.255 - Broadcast
  • 192.168.x.x, 10.x.x.x - Private IP

5. Port Numbers ​

2 bytes (16 bits): 0 ~ 65535

Range용도예시
0-1023Well-known ports80(HTTP), 443(HTTPS), 22(SSH)
1024-49151Registered ports3306(MySQL), 5432(PostgreSQL)
49152-65535Dynamic/Private클라이언트가 임시로 사용

Raw Socket 이해하기 ​

Socket의 종류 ​

go
// 1. 일반 TCP Socket (가장 흔함)
conn, _ := net.Dial("tcp", "example.com:80")
// → L7 데이터만 다룸
// → 커널이 TCP/IP/Ethernet 헤더 자동 처리

// 2. Raw IP Socket (ping, traceroute)
fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP)
// → L3부터 접근 (IP 헤더부터)
// → Ethernet 헤더는 커널이 추가

// 3. Packet Socket (tcpdump)
fd, _ := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, htons(syscall.ETH_P_ALL))
// → L2부터 접근 (Ethernet 헤더부터)
// → 모든 헤더를 직접 파싱해야 함

AF_PACKET의 특징 ​

go
// AF_PACKET 소켓 생성
fd, err := syscall.Socket(
    syscall.AF_PACKET,      // Address Family: Packet (L2 접근)
    syscall.SOCK_RAW,       // Type: Raw (가공 안 된 데이터)
    int(htons(syscall.ETH_P_ALL)),  // Protocol: 모든 이더넷 프로토콜
)

매개변수 설명:

  1. AF_PACKET: "Packet 레벨로 접근하겠다" (L2부터)
  2. SOCK_RAW: "Raw 데이터를 주세요" (커널이 처리 안 함)
  3. ETH_P_ALL: "모든 프로토콜을 캡처" (IP, ARP, IPv6 등)

다른 옵션들:

go
syscall.ETH_P_IP    // IPv4만
syscall.ETH_P_ARP   // ARP만
syscall.ETH_P_IPV6  // IPv6만

단계별 구현 ​

Step 1: 최소 기능 tcpdump (Basic) ​

go
// simple_tcpdump.go
package main

import (
    "encoding/binary"
    "fmt"
    "log"
    "net"
    "syscall"
    "unsafe"
)

// htons converts host byte order to network byte order (uint16)
func htons(v uint16) uint16 {
    return (v<<8)&0xff00 | v>>8
}

// ntohs converts network byte order to host byte order (uint16)
func ntohs(v uint16) uint16 {
    return (v<<8)&0xff00 | v>>8
}

func main() {
    // 1. Raw Packet Socket 생성 (root 권한 필요)
    fd, err := syscall.Socket(
        syscall.AF_PACKET,
        syscall.SOCK_RAW,
        int(htons(syscall.ETH_P_ALL)),
    )
    if err != nil {
        log.Fatalf("Socket creation failed: %v (root 권한으로 실행해야 합니다)", err)
    }
    defer syscall.Close(fd)

    fmt.Println("=== Simple tcpdump Started ===")
    fmt.Println("Capturing packets... (Press Ctrl+C to stop)")
    fmt.Println()

    // 패킷 수신 버퍼 (65536 bytes = 최대 이더넷 프레임 크기)
    buffer := make([]byte, 65536)

    for {
        // 2. 패킷 수신
        n, _, err := syscall.Recvfrom(fd, buffer, 0)
        if err != nil {
            log.Printf("Recvfrom error: %v", err)
            continue
        }

        // 수신한 데이터가 최소 크기보다 작으면 무시
        if n < 14 { // Ethernet 헤더 최소 크기
            continue
        }

        // 3. 패킷 파싱 및 출력
        parseAndPrintPacket(buffer[:n])
    }
}

func parseAndPrintPacket(packet []byte) {
    // Ethernet Header (14 bytes)
    if len(packet) < 14 {
        return
    }

    dstMAC := packet[0:6]
    srcMAC := packet[6:12]
    etherType := binary.BigEndian.Uint16(packet[12:14])

    fmt.Println("╔════════════════════════════════════════════════════════════╗")
    fmt.Printf("║ Ethernet Header (L2)\n")
    fmt.Printf("║   Destination MAC: %s\n", formatMAC(dstMAC))
    fmt.Printf("║   Source MAC:      %s\n", formatMAC(srcMAC))
    fmt.Printf("║   EtherType:       0x%04X ", etherType)

    switch etherType {
    case 0x0800:
        fmt.Printf("(IPv4)\n")
        if len(packet) >= 14+20 { // Ethernet + IP 최소 크기
            parseIPv4(packet[14:])
        }
    case 0x0806:
        fmt.Printf("(ARP)\n")
    case 0x86DD:
        fmt.Printf("(IPv6)\n")
    default:
        fmt.Printf("(Unknown)\n")
    }

    fmt.Println("╚════════════════════════════════════════════════════════════╝")
    fmt.Println()
}

func parseIPv4(packet []byte) {
    if len(packet) < 20 {
        return
    }

    // IP Header
    version := packet[0] >> 4
    ihl := packet[0] & 0x0F
    headerLen := int(ihl) * 4

    if len(packet) < headerLen {
        return
    }

    protocol := packet[9]
    srcIP := net.IP(packet[12:16])
    dstIP := net.IP(packet[16:20])

    fmt.Printf("║\n║ IP Header (L3)\n")
    fmt.Printf("║   Version:     %d\n", version)
    fmt.Printf("║   Header Len:  %d bytes\n", headerLen)
    fmt.Printf("║   Protocol:    %d ", protocol)

    switch protocol {
    case 1:
        fmt.Printf("(ICMP)\n")
    case 6:
        fmt.Printf("(TCP)\n")
        if len(packet) >= headerLen+20 {
            parseTCP(packet[headerLen:])
        }
    case 17:
        fmt.Printf("(UDP)\n")
        if len(packet) >= headerLen+8 {
            parseUDP(packet[headerLen:])
        }
    default:
        fmt.Printf("(Unknown)\n")
    }

    fmt.Printf("║   Source IP:   %s\n", srcIP)
    fmt.Printf("║   Dest IP:     %s\n", dstIP)
}

func parseTCP(packet []byte) {
    if len(packet) < 20 {
        return
    }

    srcPort := binary.BigEndian.Uint16(packet[0:2])
    dstPort := binary.BigEndian.Uint16(packet[2:4])
    seq := binary.BigEndian.Uint32(packet[4:8])
    ack := binary.BigEndian.Uint32(packet[8:12])

    dataOffset := (packet[12] >> 4) * 4
    flags := packet[13]

    fmt.Printf("║\n║ TCP Header (L4)\n")
    fmt.Printf("║   Source Port: %d\n", srcPort)
    fmt.Printf("║   Dest Port:   %d\n", dstPort)
    fmt.Printf("║   Seq Number:  %d\n", seq)
    fmt.Printf("║   Ack Number:  %d\n", ack)
    fmt.Printf("║   Flags:       ")

    flagNames := []string{}
    if flags&0x01 != 0 {
        flagNames = append(flagNames, "FIN")
    }
    if flags&0x02 != 0 {
        flagNames = append(flagNames, "SYN")
    }
    if flags&0x04 != 0 {
        flagNames = append(flagNames, "RST")
    }
    if flags&0x08 != 0 {
        flagNames = append(flagNames, "PSH")
    }
    if flags&0x10 != 0 {
        flagNames = append(flagNames, "ACK")
    }
    if flags&0x20 != 0 {
        flagNames = append(flagNames, "URG")
    }

    if len(flagNames) > 0 {
        for i, name := range flagNames {
            if i > 0 {
                fmt.Printf(", ")
            }
            fmt.Printf("%s", name)
        }
        fmt.Println()
    } else {
        fmt.Println("(none)")
    }

    // Application Data
    if len(packet) > int(dataOffset) {
        dataLen := len(packet) - int(dataOffset)
        fmt.Printf("║\n║ Application Data (L7)\n")
        fmt.Printf("║   Data Length: %d bytes\n", dataLen)

        // HTTP 요청/응답 감지
        data := packet[dataOffset:]
        if dataLen > 0 {
            dataStr := string(data[:min(100, dataLen)])
            if isHTTP(dataStr) {
                fmt.Printf("║   Preview:     %s...\n", sanitize(dataStr[:min(50, len(dataStr))]))
            }
        }
    }
}

func parseUDP(packet []byte) {
    if len(packet) < 8 {
        return
    }

    srcPort := binary.BigEndian.Uint16(packet[0:2])
    dstPort := binary.BigEndian.Uint16(packet[2:4])
    length := binary.BigEndian.Uint16(packet[4:6])

    fmt.Printf("║\n║ UDP Header (L4)\n")
    fmt.Printf("║   Source Port: %d\n", srcPort)
    fmt.Printf("║   Dest Port:   %d\n", dstPort)
    fmt.Printf("║   Length:      %d bytes\n", length)

    // DNS 감지 (포트 53)
    if srcPort == 53 || dstPort == 53 {
        fmt.Printf("║   Type:        DNS\n")
    }
}

func formatMAC(mac []byte) string {
    return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
}

func isHTTP(data string) bool {
    httpMethods := []string{"GET ", "POST ", "PUT ", "DELETE ", "HEAD ", "HTTP/"}
    for _, method := range httpMethods {
        if len(data) >= len(method) && data[:len(method)] == method {
            return true
        }
    }
    return false
}

func sanitize(s string) string {
    // 출력 가능한 ASCII 문자만 남기기
    result := make([]byte, 0, len(s))
    for i := 0; i < len(s); i++ {
        if s[i] >= 32 && s[i] <= 126 {
            result = append(result, s[i])
        } else {
            result = append(result, '.')
        }
    }
    return string(result)
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

실행:

bash
# 컴파일
go build -o simple_tcpdump simple_tcpdump.go

# 실행 (root 권한 필요)
sudo ./simple_tcpdump

# 또는
sudo go run simple_tcpdump.go

테스트:

다른 터미널에서:

bash
# HTTP 요청 생성
curl http://example.com

# Ping (ICMP)
ping -c 3 8.8.8.8

# DNS 쿼리 (UDP)
nslookup google.com

Step 2: 인터페이스 선택 기능 추가 ​

go
// interface_tcpdump.go
package main

import (
    "encoding/binary"
    "flag"
    "fmt"
    "log"
    "net"
    "os"
    "syscall"
)

func main() {
    // 커맨드 라인 플래그
    interfaceName := flag.String("i", "", "Network interface to capture (e.g., eth0, wlan0)")
    listInterfaces := flag.Bool("D", false, "List available interfaces")
    flag.Parse()

    // 인터페이스 목록 출력
    if *listInterfaces {
        printInterfaces()
        return
    }

    // 인터페이스가 지정되지 않으면 목록 출력
    if *interfaceName == "" {
        fmt.Println("Error: Interface not specified")
        fmt.Println("Usage: sudo ./interface_tcpdump -i <interface>")
        fmt.Println("       sudo ./interface_tcpdump -D  (list interfaces)")
        fmt.Println()
        printInterfaces()
        os.Exit(1)
    }

    // Raw Socket 생성
    fd, err := syscall.Socket(
        syscall.AF_PACKET,
        syscall.SOCK_RAW,
        int(htons(syscall.ETH_P_ALL)),
    )
    if err != nil {
        log.Fatalf("Socket creation failed: %v (root 권한이 필요합니다)", err)
    }
    defer syscall.Close(fd)

    // 인터페이스에 바인딩
    iface, err := net.InterfaceByName(*interfaceName)
    if err != nil {
        log.Fatalf("Interface '%s' not found: %v", *interfaceName, err)
    }

    // sockaddr_ll 구조체 생성
    addr := syscall.SockaddrLinklayer{
        Protocol: htons(syscall.ETH_P_ALL),
        Ifindex:  iface.Index,
    }

    if err := syscall.Bind(fd, &addr); err != nil {
        log.Fatalf("Bind failed: %v", err)
    }

    fmt.Printf("=== tcpdump Started ===\n")
    fmt.Printf("Interface: %s (index: %d)\n", iface.Name, iface.Index)
    fmt.Printf("MAC Address: %s\n", iface.HardwareAddr)
    fmt.Printf("MTU: %d\n", iface.MTU)
    fmt.Println()

    // 패킷 캡처
    buffer := make([]byte, 65536)
    packetCount := 0

    for {
        n, _, err := syscall.Recvfrom(fd, buffer, 0)
        if err != nil {
            log.Printf("Recvfrom error: %v", err)
            continue
        }

        if n < 14 {
            continue
        }

        packetCount++
        fmt.Printf("Packet #%d (size: %d bytes)\n", packetCount, n)
        parseAndPrintPacket(buffer[:n])
    }
}

func printInterfaces() {
    interfaces, err := net.Interfaces()
    if err != nil {
        log.Fatalf("Failed to get interfaces: %v", err)
    }

    fmt.Println("Available network interfaces:")
    fmt.Println()
    fmt.Printf("%-10s %-18s %-8s %-10s %s\n", "Name", "MAC Address", "MTU", "Status", "Flags")
    fmt.Println("──────────────────────────────────────────────────────────────────────")

    for _, iface := range interfaces {
        status := "down"
        if iface.Flags&net.FlagUp != 0 {
            status = "up"
        }

        flags := []string{}
        if iface.Flags&net.FlagUp != 0 {
            flags = append(flags, "UP")
        }
        if iface.Flags&net.FlagLoopback != 0 {
            flags = append(flags, "LOOPBACK")
        }
        if iface.Flags&net.FlagBroadcast != 0 {
            flags = append(flags, "BROADCAST")
        }

        flagStr := ""
        for i, f := range flags {
            if i > 0 {
                flagStr += ","
            }
            flagStr += f
        }

        fmt.Printf("%-10s %-18s %-8d %-10s %s\n",
            iface.Name,
            iface.HardwareAddr,
            iface.MTU,
            status,
            flagStr,
        )
    }
}

func htons(v uint16) uint16 {
    return (v<<8)&0xff00 | v>>8
}

func parseAndPrintPacket(packet []byte) {
    if len(packet) < 14 {
        return
    }

    dstMAC := packet[0:6]
    srcMAC := packet[6:12]
    etherType := binary.BigEndian.Uint16(packet[12:14])

    fmt.Printf("  L2: %s → %s  Type: 0x%04X",
        formatMAC(srcMAC),
        formatMAC(dstMAC),
        etherType,
    )

    if etherType == 0x0800 && len(packet) >= 34 {
        // IPv4
        srcIP := net.IP(packet[26:30])
        dstIP := net.IP(packet[30:34])
        protocol := packet[23]

        fmt.Printf("\n  L3: %s → %s  Protocol: %d",
            srcIP, dstIP, protocol)

        if protocol == 6 && len(packet) >= 54 { // TCP
            ihl := int(packet[14]&0x0F) * 4
            srcPort := binary.BigEndian.Uint16(packet[14+ihl : 14+ihl+2])
            dstPort := binary.BigEndian.Uint16(packet[14+ihl+2 : 14+ihl+4])
            flags := packet[14+ihl+13]

            fmt.Printf("\n  L4: TCP %d → %d  Flags: ", srcPort, dstPort)
            printTCPFlags(flags)
        } else if protocol == 17 && len(packet) >= 42 { // UDP
            ihl := int(packet[14]&0x0F) * 4
            srcPort := binary.BigEndian.Uint16(packet[14+ihl : 14+ihl+2])
            dstPort := binary.BigEndian.Uint16(packet[14+ihl+2 : 14+ihl+4])

            fmt.Printf("\n  L4: UDP %d → %d", srcPort, dstPort)
        }
    }

    fmt.Println()
    fmt.Println()
}

func printTCPFlags(flags byte) {
    flagNames := []string{}
    if flags&0x01 != 0 {
        flagNames = append(flagNames, "FIN")
    }
    if flags&0x02 != 0 {
        flagNames = append(flagNames, "SYN")
    }
    if flags&0x04 != 0 {
        flagNames = append(flagNames, "RST")
    }
    if flags&0x08 != 0 {
        flagNames = append(flagNames, "PSH")
    }
    if flags&0x10 != 0 {
        flagNames = append(flagNames, "ACK")
    }
    if flags&0x20 != 0 {
        flagNames = append(flagNames, "URG")
    }

    for i, name := range flagNames {
        if i > 0 {
            fmt.Printf(",")
        }
        fmt.Printf("%s", name)
    }
}

func formatMAC(mac []byte) string {
    return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
}

사용법:

bash
# 인터페이스 목록 확인
sudo ./interface_tcpdump -D

# 특정 인터페이스 캡처
sudo ./interface_tcpdump -i eth0

# 무선랜 캡처
sudo ./interface_tcpdump -i wlan0

Step 3: 필터 기능 추가 (BPF-like) ​

go
// filter_tcpdump.go
package main

import (
    "encoding/binary"
    "flag"
    "fmt"
    "log"
    "net"
    "os"
    "strconv"
    "strings"
    "syscall"
    "time"
)

// PacketFilter 인터페이스
type PacketFilter interface {
    Match(packet []byte) bool
    Description() string
}

// ProtocolFilter - 프로토콜 필터 (tcp, udp, icmp)
type ProtocolFilter struct {
    Protocol string
}

func (f *ProtocolFilter) Match(packet []byte) bool {
    if len(packet) < 34 {
        return false
    }

    etherType := binary.BigEndian.Uint16(packet[12:14])
    if etherType != 0x0800 { // IPv4만
        return false
    }

    protocol := packet[23]

    switch strings.ToLower(f.Protocol) {
    case "tcp":
        return protocol == 6
    case "udp":
        return protocol == 17
    case "icmp":
        return protocol == 1
    default:
        return true
    }
}

func (f *ProtocolFilter) Description() string {
    return fmt.Sprintf("Protocol: %s", f.Protocol)
}

// PortFilter - 포트 필터
type PortFilter struct {
    Port uint16
}

func (f *PortFilter) Match(packet []byte) bool {
    if len(packet) < 34 {
        return false
    }

    etherType := binary.BigEndian.Uint16(packet[12:14])
    if etherType != 0x0800 {
        return false
    }

    protocol := packet[23]
    if protocol != 6 && protocol != 17 { // TCP or UDP
        return false
    }

    ihl := int(packet[14]&0x0F) * 4
    if len(packet) < 14+ihl+4 {
        return false
    }

    srcPort := binary.BigEndian.Uint16(packet[14+ihl : 14+ihl+2])
    dstPort := binary.BigEndian.Uint16(packet[14+ihl+2 : 14+ihl+4])

    return srcPort == f.Port || dstPort == f.Port
}

func (f *PortFilter) Description() string {
    return fmt.Sprintf("Port: %d", f.Port)
}

// HostFilter - IP 주소 필터
type HostFilter struct {
    IP net.IP
}

func (f *HostFilter) Match(packet []byte) bool {
    if len(packet) < 34 {
        return false
    }

    etherType := binary.BigEndian.Uint16(packet[12:14])
    if etherType != 0x0800 {
        return false
    }

    srcIP := net.IP(packet[26:30])
    dstIP := net.IP(packet[30:34])

    return srcIP.Equal(f.IP) || dstIP.Equal(f.IP)
}

func (f *HostFilter) Description() string {
    return fmt.Sprintf("Host: %s", f.IP)
}

// PacketCapture - 메인 캡처 구조체
type PacketCapture struct {
    fd            int
    filters       []PacketFilter
    packetCount   int
    bytesReceived uint64
    startTime     time.Time
}

func NewPacketCapture(interfaceName string) (*PacketCapture, error) {
    fd, err := syscall.Socket(
        syscall.AF_PACKET,
        syscall.SOCK_RAW,
        int(htons(syscall.ETH_P_ALL)),
    )
    if err != nil {
        return nil, fmt.Errorf("socket creation failed: %w", err)
    }

    iface, err := net.InterfaceByName(interfaceName)
    if err != nil {
        syscall.Close(fd)
        return nil, fmt.Errorf("interface not found: %w", err)
    }

    addr := syscall.SockaddrLinklayer{
        Protocol: htons(syscall.ETH_P_ALL),
        Ifindex:  iface.Index,
    }

    if err := syscall.Bind(fd, &addr); err != nil {
        syscall.Close(fd)
        return nil, fmt.Errorf("bind failed: %w", err)
    }

    return &PacketCapture{
        fd:        fd,
        filters:   []PacketFilter{},
        startTime: time.Now(),
    }, nil
}

func (pc *PacketCapture) AddFilter(filter PacketFilter) {
    pc.filters = append(pc.filters, filter)
}

func (pc *PacketCapture) matchesFilters(packet []byte) bool {
    if len(pc.filters) == 0 {
        return true
    }

    for _, filter := range pc.filters {
        if !filter.Match(packet) {
            return false
        }
    }
    return true
}

func (pc *PacketCapture) Start() {
    buffer := make([]byte, 65536)

    fmt.Println("=== Packet Capture Started ===")
    if len(pc.filters) > 0 {
        fmt.Println("Active Filters:")
        for _, filter := range pc.filters {
            fmt.Printf("  - %s\n", filter.Description())
        }
    } else {
        fmt.Println("No filters (capturing all packets)")
    }
    fmt.Println()

    for {
        n, _, err := syscall.Recvfrom(pc.fd, buffer, 0)
        if err != nil {
            log.Printf("Recvfrom error: %v", err)
            continue
        }

        if n < 14 {
            continue
        }

        packet := buffer[:n]

        // 필터 매칭
        if !pc.matchesFilters(packet) {
            continue
        }

        pc.packetCount++
        pc.bytesReceived += uint64(n)

        pc.printPacket(packet)

        // 통계 출력 (100개마다)
        if pc.packetCount%100 == 0 {
            pc.printStatistics()
        }
    }
}

func (pc *PacketCapture) printPacket(packet []byte) {
    timestamp := time.Now().Format("15:04:05.000000")

    dstMAC := packet[0:6]
    srcMAC := packet[6:12]
    etherType := binary.BigEndian.Uint16(packet[12:14])

    fmt.Printf("[%s] Packet #%d (%d bytes)\n", timestamp, pc.packetCount, len(packet))
    fmt.Printf("  %s → %s", formatMAC(srcMAC), formatMAC(dstMAC))

    if etherType == 0x0800 && len(packet) >= 34 {
        srcIP := net.IP(packet[26:30])
        dstIP := net.IP(packet[30:34])
        protocol := packet[23]

        fmt.Printf("\n  %s → %s", srcIP, dstIP)

        if protocol == 6 && len(packet) >= 54 { // TCP
            ihl := int(packet[14]&0x0F) * 4
            srcPort := binary.BigEndian.Uint16(packet[14+ihl : 14+ihl+2])
            dstPort := binary.BigEndian.Uint16(packet[14+ihl+2 : 14+ihl+4])
            flags := packet[14+ihl+13]

            fmt.Printf("  TCP %d → %d [", srcPort, dstPort)
            printTCPFlags(flags)
            fmt.Printf("]")
        } else if protocol == 17 && len(packet) >= 42 { // UDP
            ihl := int(packet[14]&0x0F) * 4
            srcPort := binary.BigEndian.Uint16(packet[14+ihl : 14+ihl+2])
            dstPort := binary.BigEndian.Uint16(packet[14+ihl+2 : 14+ihl+4])

            fmt.Printf("  UDP %d → %d", srcPort, dstPort)
        } else if protocol == 1 { // ICMP
            fmt.Printf("  ICMP")
        }
    }

    fmt.Println()
}

func (pc *PacketCapture) printStatistics() {
    duration := time.Since(pc.startTime).Seconds()
    pps := float64(pc.packetCount) / duration
    bps := float64(pc.bytesReceived) / duration

    fmt.Println()
    fmt.Println("─────────── Statistics ───────────")
    fmt.Printf("Packets: %d (%.2f pkt/s)\n", pc.packetCount, pps)
    fmt.Printf("Bytes:   %d (%.2f KB/s)\n", pc.bytesReceived, bps/1024)
    fmt.Printf("Runtime: %.2f seconds\n", duration)
    fmt.Println("──────────────────────────────────")
    fmt.Println()
}

func (pc *PacketCapture) Close() {
    pc.printStatistics()
    syscall.Close(pc.fd)
}

func main() {
    interfaceName := flag.String("i", "", "Network interface")
    protocolFilter := flag.String("proto", "", "Protocol filter (tcp/udp/icmp)")
    portFilter := flag.Int("port", 0, "Port filter")
    hostFilter := flag.String("host", "", "Host IP filter")
    flag.Parse()

    if *interfaceName == "" {
        fmt.Println("Usage: sudo ./filter_tcpdump -i <interface> [filters]")
        fmt.Println("Filters:")
        fmt.Println("  -proto <tcp|udp|icmp>  Filter by protocol")
        fmt.Println("  -port <port>           Filter by port")
        fmt.Println("  -host <ip>             Filter by IP address")
        fmt.Println()
        fmt.Println("Examples:")
        fmt.Println("  sudo ./filter_tcpdump -i eth0 -proto tcp")
        fmt.Println("  sudo ./filter_tcpdump -i eth0 -port 80")
        fmt.Println("  sudo ./filter_tcpdump -i eth0 -host 8.8.8.8")
        fmt.Println("  sudo ./filter_tcpdump -i eth0 -proto tcp -port 443")
        os.Exit(1)
    }

    pc, err := NewPacketCapture(*interfaceName)
    if err != nil {
        log.Fatalf("Failed to create packet capture: %v", err)
    }
    defer pc.Close()

    // 필터 추가
    if *protocolFilter != "" {
        pc.AddFilter(&ProtocolFilter{Protocol: *protocolFilter})
    }
    if *portFilter != 0 {
        pc.AddFilter(&PortFilter{Port: uint16(*portFilter)})
    }
    if *hostFilter != "" {
        ip := net.ParseIP(*hostFilter)
        if ip == nil {
            log.Fatalf("Invalid IP address: %s", *hostFilter)
        }
        pc.AddFilter(&HostFilter{IP: ip})
    }

    pc.Start()
}

func htons(v uint16) uint16 {
    return (v<<8)&0xff00 | v>>8
}

func printTCPFlags(flags byte) {
    flagNames := []string{}
    if flags&0x02 != 0 {
        flagNames = append(flagNames, "SYN")
    }
    if flags&0x10 != 0 {
        flagNames = append(flagNames, "ACK")
    }
    if flags&0x08 != 0 {
        flagNames = append(flagNames, "PSH")
    }
    if flags&0x01 != 0 {
        flagNames = append(flagNames, "FIN")
    }
    if flags&0x04 != 0 {
        flagNames = append(flagNames, "RST")
    }

    for i, name := range flagNames {
        if i > 0 {
            fmt.Printf(",")
        }
        fmt.Printf("%s", name)
    }
}

func formatMAC(mac []byte) string {
    return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
}

사용 예시:

bash
# TCP 패킷만 캡처
sudo ./filter_tcpdump -i eth0 -proto tcp

# HTTP 트래픽만 (포트 80)
sudo ./filter_tcpdump -i eth0 -port 80

# 특정 IP와의 통신만
sudo ./filter_tcpdump -i eth0 -host 8.8.8.8

# 조합: HTTPS 트래픽만
sudo ./filter_tcpdump -i eth0 -proto tcp -port 443

Step 4: PCAP 파일 저장 기능 ​

go
// pcap_tcpdump.go
package main

import (
    "encoding/binary"
    "flag"
    "fmt"
    "log"
    "net"
    "os"
    "syscall"
    "time"
)

// PCAP 파일 형식 구조체

// PCAP Global Header (24 bytes)
type PCAPGlobalHeader struct {
    MagicNumber  uint32 // 0xa1b2c3d4
    VersionMajor uint16 // 2
    VersionMinor uint16 // 4
    ThisZone     int32  // GMT to local correction
    SigFigs      uint32 // accuracy of timestamps
    SnapLen      uint32 // max length of captured packets
    Network      uint32 // data link type (1 = Ethernet)
}

// PCAP Packet Header (16 bytes)
type PCAPPacketHeader struct {
    TsSec   uint32 // timestamp seconds
    TsUsec  uint32 // timestamp microseconds
    InclLen uint32 // number of octets of packet saved in file
    OrigLen uint32 // actual length of packet
}

type PCAPWriter struct {
    file *os.File
}

func NewPCAPWriter(filename string) (*PCAPWriter, error) {
    file, err := os.Create(filename)
    if err != nil {
        return nil, err
    }

    // PCAP Global Header 작성
    header := PCAPGlobalHeader{
        MagicNumber:  0xa1b2c3d4,
        VersionMajor: 2,
        VersionMinor: 4,
        ThisZone:     0,
        SigFigs:      0,
        SnapLen:      65535,
        Network:      1, // Ethernet
    }

    if err := binary.Write(file, binary.LittleEndian, &header); err != nil {
        file.Close()
        return nil, err
    }

    return &PCAPWriter{file: file}, nil
}

func (pw *PCAPWriter) WritePacket(packet []byte, timestamp time.Time) error {
    // PCAP Packet Header 작성
    header := PCAPPacketHeader{
        TsSec:   uint32(timestamp.Unix()),
        TsUsec:  uint32(timestamp.Nanosecond() / 1000),
        InclLen: uint32(len(packet)),
        OrigLen: uint32(len(packet)),
    }

    if err := binary.Write(pw.file, binary.LittleEndian, &header); err != nil {
        return err
    }

    // 패킷 데이터 작성
    _, err := pw.file.Write(packet)
    return err
}

func (pw *PCAPWriter) Close() error {
    return pw.file.Close()
}

func main() {
    interfaceName := flag.String("i", "", "Network interface")
    outputFile := flag.String("w", "", "Write packets to file (PCAP format)")
    count := flag.Int("c", 0, "Capture N packets then exit (0 = unlimited)")
    flag.Parse()

    if *interfaceName == "" {
        fmt.Println("Usage: sudo ./pcap_tcpdump -i <interface> [-w output.pcap] [-c count]")
        os.Exit(1)
    }

    // Raw Socket 생성
    fd, err := syscall.Socket(
        syscall.AF_PACKET,
        syscall.SOCK_RAW,
        int(htons(syscall.ETH_P_ALL)),
    )
    if err != nil {
        log.Fatalf("Socket creation failed: %v", err)
    }
    defer syscall.Close(fd)

    // 인터페이스 바인딩
    iface, err := net.InterfaceByName(*interfaceName)
    if err != nil {
        log.Fatalf("Interface not found: %v", err)
    }

    addr := syscall.SockaddrLinklayer{
        Protocol: htons(syscall.ETH_P_ALL),
        Ifindex:  iface.Index,
    }

    if err := syscall.Bind(fd, &addr); err != nil {
        log.Fatalf("Bind failed: %v", err)
    }

    // PCAP 파일 생성 (옵션)
    var pcapWriter *PCAPWriter
    if *outputFile != "" {
        pcapWriter, err = NewPCAPWriter(*outputFile)
        if err != nil {
            log.Fatalf("Failed to create PCAP file: %v", err)
        }
        defer pcapWriter.Close()
        fmt.Printf("Writing packets to: %s\n", *outputFile)
    }

    fmt.Printf("Capturing on interface: %s\n", *interfaceName)
    if *count > 0 {
        fmt.Printf("Capturing %d packets...\n", *count)
    } else {
        fmt.Println("Capturing packets... (Press Ctrl+C to stop)")
    }
    fmt.Println()

    buffer := make([]byte, 65536)
    packetCount := 0

    for {
        n, _, err := syscall.Recvfrom(fd, buffer, 0)
        if err != nil {
            log.Printf("Recvfrom error: %v", err)
            continue
        }

        if n < 14 {
            continue
        }

        packet := buffer[:n]
        timestamp := time.Now()
        packetCount++

        // 화면 출력
        printPacketSummary(packetCount, packet, timestamp)

        // PCAP 파일에 저장
        if pcapWriter != nil {
            if err := pcapWriter.WritePacket(packet, timestamp); err != nil {
                log.Printf("Failed to write packet: %v", err)
            }
        }

        // 카운트 제한 확인
        if *count > 0 && packetCount >= *count {
            fmt.Printf("\nCaptured %d packets\n", packetCount)
            break
        }
    }
}

func printPacketSummary(num int, packet []byte, ts time.Time) {
    timestamp := ts.Format("15:04:05.000000")
    fmt.Printf("%s #%-5d %4d bytes  ", timestamp, num, len(packet))

    if len(packet) < 14 {
        fmt.Println()
        return
    }

    srcMAC := packet[6:12]
    dstMAC := packet[0:6]
    etherType := binary.BigEndian.Uint16(packet[12:14])

    if etherType == 0x0800 && len(packet) >= 34 {
        srcIP := net.IP(packet[26:30])
        dstIP := net.IP(packet[30:34])
        protocol := packet[23]

        fmt.Printf("%15s → %-15s", srcIP, dstIP)

        if protocol == 6 && len(packet) >= 54 { // TCP
            ihl := int(packet[14]&0x0F) * 4
            srcPort := binary.BigEndian.Uint16(packet[14+ihl : 14+ihl+2])
            dstPort := binary.BigEndian.Uint16(packet[14+ihl+2 : 14+ihl+4])
            flags := packet[14+ihl+13]

            fmt.Printf("  TCP %5d → %-5d [", srcPort, dstPort)
            printTCPFlagsShort(flags)
            fmt.Printf("]")
        } else if protocol == 17 { // UDP
            ihl := int(packet[14]&0x0F) * 4
            if len(packet) >= 14+ihl+4 {
                srcPort := binary.BigEndian.Uint16(packet[14+ihl : 14+ihl+2])
                dstPort := binary.BigEndian.Uint16(packet[14+ihl+2 : 14+ihl+4])
                fmt.Printf("  UDP %5d → %-5d", srcPort, dstPort)
            }
        } else if protocol == 1 {
            fmt.Printf("  ICMP")
        }
    } else {
        fmt.Printf("%s → %s  Type: 0x%04X",
            formatMAC(srcMAC),
            formatMAC(dstMAC),
            etherType)
    }

    fmt.Println()
}

func printTCPFlagsShort(flags byte) {
    if flags&0x02 != 0 {
        fmt.Printf("S")
    }
    if flags&0x10 != 0 {
        fmt.Printf("A")
    }
    if flags&0x08 != 0 {
        fmt.Printf("P")
    }
    if flags&0x01 != 0 {
        fmt.Printf("F")
    }
    if flags&0x04 != 0 {
        fmt.Printf("R")
    }
}

func htons(v uint16) uint16 {
    return (v<<8)&0xff00 | v>>8
}

func formatMAC(mac []byte) string {
    return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
}

사용법:

bash
# 화면에만 출력
sudo ./pcap_tcpdump -i eth0

# PCAP 파일로 저장
sudo ./pcap_tcpdump -i eth0 -w capture.pcap

# 100개 패킷만 캡처
sudo ./pcap_tcpdump -i eth0 -w capture.pcap -c 100

# 저장된 파일을 Wireshark로 열기
wireshark capture.pcap

# 또는 tcpdump로 읽기
tcpdump -r capture.pcap

고급 기능 추가 ​

Step 5: TCP 연결 추적 (Connection Tracking) ​

go
// connection_tracking.go
package main

import (
    "encoding/binary"
    "fmt"
    "net"
    "sync"
    "time"
)

// TCPConnection represents a TCP connection
type TCPConnection struct {
    SrcIP       net.IP
    DstIP       net.IP
    SrcPort     uint16
    DstPort     uint16
    State       string
    StartTime   time.Time
    LastSeen    time.Time
    PacketCount int
    BytesCount  uint64
}

func (tc *TCPConnection) Key() string {
    return fmt.Sprintf("%s:%d-%s:%d",
        tc.SrcIP, tc.SrcPort,
        tc.DstIP, tc.DstPort)
}

func (tc *TCPConnection) ReverseKey() string {
    return fmt.Sprintf("%s:%d-%s:%d",
        tc.DstIP, tc.DstPort,
        tc.SrcIP, tc.SrcPort)
}

// ConnectionTracker tracks TCP connections
type ConnectionTracker struct {
    connections map[string]*TCPConnection
    mu          sync.RWMutex
}

func NewConnectionTracker() *ConnectionTracker {
    return &ConnectionTracker{
        connections: make(map[string]*TCPConnection),
    }
}

func (ct *ConnectionTracker) ProcessPacket(packet []byte) {
    if len(packet) < 54 {
        return
    }

    etherType := binary.BigEndian.Uint16(packet[12:14])
    if etherType != 0x0800 {
        return
    }

    protocol := packet[23]
    if protocol != 6 { // TCP만
        return
    }

    srcIP := net.IP(packet[26:30])
    dstIP := net.IP(packet[30:34])

    ihl := int(packet[14]&0x0F) * 4
    if len(packet) < 14+ihl+20 {
        return
    }

    tcpHeader := packet[14+ihl:]
    srcPort := binary.BigEndian.Uint16(tcpHeader[0:2])
    dstPort := binary.BigEndian.Uint16(tcpHeader[2:4])
    flags := tcpHeader[13]

    conn := &TCPConnection{
        SrcIP:     srcIP,
        DstIP:     dstIP,
        SrcPort:   srcPort,
        DstPort:   dstPort,
        LastSeen:  time.Now(),
        BytesCount: uint64(len(packet)),
    }

    key := conn.Key()
    reverseKey := conn.ReverseKey()

    ct.mu.Lock()
    defer ct.mu.Unlock()

    // 기존 연결 찾기 (양방향)
    existing, ok := ct.connections[key]
    if !ok {
        existing, ok = ct.connections[reverseKey]
    }

    if flags&0x02 != 0 && flags&0x10 == 0 { // SYN (SYN-ACK 아님)
        // 새 연결 시작
        conn.State = "SYN_SENT"
        conn.StartTime = time.Now()
        conn.PacketCount = 1
        ct.connections[key] = conn
        fmt.Printf("\n[NEW CONNECTION] %s:%d → %s:%d (SYN)\n",
            srcIP, srcPort, dstIP, dstPort)
    } else if ok {
        // 기존 연결 업데이트
        existing.LastSeen = time.Now()
        existing.PacketCount++
        existing.BytesCount += uint64(len(packet))

        // 상태 업데이트
        if flags&0x02 != 0 && flags&0x10 != 0 { // SYN-ACK
            existing.State = "SYN_RECEIVED"
            fmt.Printf("[CONNECTION] %s (SYN-ACK)\n", existing.Key())
        } else if flags&0x10 != 0 && existing.State == "SYN_RECEIVED" {
            existing.State = "ESTABLISHED"
            fmt.Printf("[CONNECTION] %s ESTABLISHED\n", existing.Key())
        } else if flags&0x01 != 0 { // FIN
            existing.State = "CLOSING"
            fmt.Printf("[CONNECTION] %s CLOSING (FIN)\n", existing.Key())
        } else if flags&0x04 != 0 { // RST
            existing.State = "CLOSED"
            fmt.Printf("[CONNECTION] %s RESET (RST)\n", existing.Key())
            delete(ct.connections, key)
            delete(ct.connections, reverseKey)
        }
    }
}

func (ct *ConnectionTracker) PrintStatistics() {
    ct.mu.RLock()
    defer ct.mu.RUnlock()

    fmt.Println("\n╔════════════════════════════════════════════════════════════════════════╗")
    fmt.Println("║                      Active TCP Connections                            ║")
    fmt.Println("╠════════════════════════════════════════════════════════════════════════╣")

    if len(ct.connections) == 0 {
        fmt.Println("║  No active connections                                                 ║")
    } else {
        for _, conn := range ct.connections {
            duration := time.Since(conn.StartTime)
            fmt.Printf("║ %s:%d → %s:%d\n",
                conn.SrcIP, conn.SrcPort,
                conn.DstIP, conn.DstPort)
            fmt.Printf("║   State: %-15s  Packets: %-8d  Bytes: %-10d\n",
                conn.State, conn.PacketCount, conn.BytesCount)
            fmt.Printf("║   Duration: %s\n", duration.Round(time.Second))
            fmt.Println("║ ────────────────────────────────────────────────────────────────────")
        }
    }

    fmt.Println("╚════════════════════════════════════════════════════════════════════════╝")
}

// main 함수에서 사용:
// tracker := NewConnectionTracker()
//
// // 패킷 처리 시
// tracker.ProcessPacket(packet)
//
// // 주기적으로 통계 출력
// go func() {
//     ticker := time.NewTicker(10 * time.Second)
//     for range ticker.C {
//         tracker.PrintStatistics()
//     }
// }()

Step 6: HTTP 요청/응답 추출 ​

go
// http_parser.go
package main

import (
    "bufio"
    "bytes"
    "encoding/binary"
    "fmt"
    "strings"
)

type HTTPRequest struct {
    Method  string
    URL     string
    Version string
    Headers map[string]string
}

type HTTPResponse struct {
    Version    string
    StatusCode int
    Status     string
    Headers    map[string]string
}

func parseHTTPRequest(data []byte) (*HTTPRequest, error) {
    reader := bufio.NewReader(bytes.NewReader(data))

    // 첫 줄: GET /path HTTP/1.1
    line, err := reader.ReadString('\n')
    if err != nil {
        return nil, err
    }

    parts := strings.SplitN(strings.TrimSpace(line), " ", 3)
    if len(parts) != 3 {
        return nil, fmt.Errorf("invalid request line")
    }

    req := &HTTPRequest{
        Method:  parts[0],
        URL:     parts[1],
        Version: parts[2],
        Headers: make(map[string]string),
    }

    // 헤더 파싱
    for {
        line, err := reader.ReadString('\n')
        if err != nil || line == "\r\n" || line == "\n" {
            break
        }

        parts := strings.SplitN(strings.TrimSpace(line), ":", 2)
        if len(parts) == 2 {
            req.Headers[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
        }
    }

    return req, nil
}

func parseHTTPResponse(data []byte) (*HTTPResponse, error) {
    reader := bufio.NewReader(bytes.NewReader(data))

    // 첫 줄: HTTP/1.1 200 OK
    line, err := reader.ReadString('\n')
    if err != nil {
        return nil, err
    }

    parts := strings.SplitN(strings.TrimSpace(line), " ", 3)
    if len(parts) < 2 {
        return nil, fmt.Errorf("invalid response line")
    }

    var statusCode int
    fmt.Sscanf(parts[1], "%d", &statusCode)

    status := ""
    if len(parts) == 3 {
        status = parts[2]
    }

    resp := &HTTPResponse{
        Version:    parts[0],
        StatusCode: statusCode,
        Status:     status,
        Headers:    make(map[string]string),
    }

    // 헤더 파싱
    for {
        line, err := reader.ReadString('\n')
        if err != nil || line == "\r\n" || line == "\n" {
            break
        }

        parts := strings.SplitN(strings.TrimSpace(line), ":", 2)
        if len(parts) == 2 {
            resp.Headers[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
        }
    }

    return resp, nil
}

func extractHTTPFromPacket(packet []byte) {
    if len(packet) < 54 {
        return
    }

    etherType := binary.BigEndian.Uint16(packet[12:14])
    if etherType != 0x0800 {
        return
    }

    protocol := packet[23]
    if protocol != 6 { // TCP
        return
    }

    ihl := int(packet[14]&0x0F) * 4
    tcpHeader := packet[14+ihl:]

    if len(tcpHeader) < 20 {
        return
    }

    srcPort := binary.BigEndian.Uint16(tcpHeader[0:2])
    dstPort := binary.BigEndian.Uint16(tcpHeader[2:4])

    // HTTP 포트 확인 (80, 8080)
    if srcPort != 80 && dstPort != 80 && srcPort != 8080 && dstPort != 8080 {
        return
    }

    dataOffset := int(tcpHeader[12]>>4) * 4
    if len(tcpHeader) <= dataOffset {
        return
    }

    httpData := tcpHeader[dataOffset:]
    if len(httpData) == 0 {
        return
    }

    dataStr := string(httpData)

    // HTTP 요청 확인
    if strings.HasPrefix(dataStr, "GET ") ||
        strings.HasPrefix(dataStr, "POST ") ||
        strings.HasPrefix(dataStr, "PUT ") ||
        strings.HasPrefix(dataStr, "DELETE ") {

        req, err := parseHTTPRequest(httpData)
        if err == nil {
            fmt.Println("\n╔════════════════════════════════════════════════════════╗")
            fmt.Println("║                   HTTP REQUEST                         ║")
            fmt.Println("╠════════════════════════════════════════════════════════╣")
            fmt.Printf("║ %s %s %s\n", req.Method, req.URL, req.Version)
            for key, value := range req.Headers {
                fmt.Printf("║ %s: %s\n", key, value)
            }
            fmt.Println("╚════════════════════════════════════════════════════════╝")
        }
    } else if strings.HasPrefix(dataStr, "HTTP/") {
        resp, err := parseHTTPResponse(httpData)
        if err == nil {
            fmt.Println("\n╔════════════════════════════════════════════════════════╗")
            fmt.Println("║                   HTTP RESPONSE                        ║")
            fmt.Println("╠════════════════════════════════════════════════════════╣")
            fmt.Printf("║ %s %d %s\n", resp.Version, resp.StatusCode, resp.Status)
            for key, value := range resp.Headers {
                fmt.Printf("║ %s: %s\n", key, value)
            }
            fmt.Println("╚════════════════════════════════════════════════════════╝")
        }
    }
}

실전 활용 ​

사용 시나리오 ​

1. 웹 서버 디버깅 ​

bash
# 웹 서버로 들어오는 HTTP 요청 확인
sudo ./filter_tcpdump -i eth0 -port 80

# 다른 터미널에서
curl http://localhost/api/users

2. 데이터베이스 연결 확인 ​

bash
# MySQL 트래픽 캡처 (포트 3306)
sudo ./filter_tcpdump -i lo -port 3306

# PostgreSQL (포트 5432)
sudo ./filter_tcpdump -i lo -port 5432

3. DNS 쿼리 분석 ​

bash
# DNS 트래픽 캡처 (UDP 포트 53)
sudo ./filter_tcpdump -i eth0 -proto udp -port 53

# 다른 터미널에서
nslookup google.com
dig example.com

4. 네트워크 문제 진단 ​

bash
# 3-way handshake 확인
sudo ./filter_tcpdump -i eth0 -proto tcp -host 192.168.1.100

# SYN, SYN-ACK, ACK 패킷 확인
# 연결 실패 시 RST 패킷 확인

정리 및 학습 포인트 ​

배운 네트워크 개념 ​

  1. 패킷 구조

    • Ethernet (L2), IP (L3), TCP/UDP (L4), Application (L7)
    • 각 계층의 헤더 포맷
  2. Raw Socket

    • AF_PACKET으로 L2부터 접근
    • 커널 처리 전 패킷 복사본 수신
  3. Byte Order

    • Network Byte Order (Big Endian)
    • Host Byte Order 변환
  4. TCP 프로토콜

    • 3-way handshake (SYN, SYN-ACK, ACK)
    • 연결 종료 (FIN, RST)
    • 플래그의 의미

Golang 특징 ​

  1. syscall 패키지

    • 저수준 시스템 콜 직접 호출
    • C의 socket API와 유사
  2. binary 패키지

    • 바이트 순서 변환
    • 구조체 직렬화/역직렬화
  3. net 패키지

    • 네트워크 인터페이스 관리
    • IP, MAC 주소 처리

추가 개선 아이디어 ​

  1. BPF (Berkeley Packet Filter) 구현

    • 커널 레벨 필터링으로 성능 향상
  2. 멀티스레드 처리

    • 패킷 캡처와 분석을 별도 고루틴에서
  3. 실시간 통계

    • 대시보드 UI (termui 라이브러리 사용)
  4. 프로토콜 디코더 추가

    • DNS, DHCP, TLS 등
  5. 패킷 재전송 탐지

    • 네트워크 품질 분석

참고 자료 ​

관련 도구 ​

  • tcpdump: 실제 tcpdump 명령어
  • Wireshark: GUI 패킷 분석기
  • tshark: Wireshark의 CLI 버전

Golang 라이브러리 ​

  • gopacket: Google의 패킷 처리 라이브러리
    bash
    go get github.com/google/gopacket

RFC 문서 ​

  • RFC 791: IP
  • RFC 793: TCP
  • RFC 768: UDP
  • RFC 826: ARP

이제 네트워크 패킷의 내부 구조와 tcpdump의 동작 원리를 완벽히 이해하셨을 것입니다!