Golang으로 tcpdump 만들기 - 네트워크 패킷 분석 완벽 가이드
목차
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- Broadcast192.168.x.x,10.x.x.x- Private IP
5. Port Numbers
2 bytes (16 bits): 0 ~ 65535
| Range | 용도 | 예시 |
|---|---|---|
| 0-1023 | Well-known ports | 80(HTTP), 443(HTTPS), 22(SSH) |
| 1024-49151 | Registered ports | 3306(MySQL), 5432(PostgreSQL) |
| 49152-65535 | Dynamic/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: 모든 이더넷 프로토콜
)매개변수 설명:
- AF_PACKET: "Packet 레벨로 접근하겠다" (L2부터)
- SOCK_RAW: "Raw 데이터를 주세요" (커널이 처리 안 함)
- 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.comStep 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 wlan0Step 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 443Step 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/users2. 데이터베이스 연결 확인
bash
# MySQL 트래픽 캡처 (포트 3306)
sudo ./filter_tcpdump -i lo -port 3306
# PostgreSQL (포트 5432)
sudo ./filter_tcpdump -i lo -port 54323. DNS 쿼리 분석
bash
# DNS 트래픽 캡처 (UDP 포트 53)
sudo ./filter_tcpdump -i eth0 -proto udp -port 53
# 다른 터미널에서
nslookup google.com
dig example.com4. 네트워크 문제 진단
bash
# 3-way handshake 확인
sudo ./filter_tcpdump -i eth0 -proto tcp -host 192.168.1.100
# SYN, SYN-ACK, ACK 패킷 확인
# 연결 실패 시 RST 패킷 확인정리 및 학습 포인트
배운 네트워크 개념
패킷 구조
- Ethernet (L2), IP (L3), TCP/UDP (L4), Application (L7)
- 각 계층의 헤더 포맷
Raw Socket
- AF_PACKET으로 L2부터 접근
- 커널 처리 전 패킷 복사본 수신
Byte Order
- Network Byte Order (Big Endian)
- Host Byte Order 변환
TCP 프로토콜
- 3-way handshake (SYN, SYN-ACK, ACK)
- 연결 종료 (FIN, RST)
- 플래그의 의미
Golang 특징
syscall 패키지
- 저수준 시스템 콜 직접 호출
- C의 socket API와 유사
binary 패키지
- 바이트 순서 변환
- 구조체 직렬화/역직렬화
net 패키지
- 네트워크 인터페이스 관리
- IP, MAC 주소 처리
추가 개선 아이디어
BPF (Berkeley Packet Filter) 구현
- 커널 레벨 필터링으로 성능 향상
멀티스레드 처리
- 패킷 캡처와 분석을 별도 고루틴에서
실시간 통계
- 대시보드 UI (termui 라이브러리 사용)
프로토콜 디코더 추가
- DNS, DHCP, TLS 등
패킷 재전송 탐지
- 네트워크 품질 분석
참고 자료
관련 도구
- 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의 동작 원리를 완벽히 이해하셨을 것입니다!