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의 동작 원리를 완벽히 이해하셨을 것입니다!