홈으로 돌아가기

Go TUN UDP 중계: YAML 설정 및 코드

이 기사는 YAML 설정을 통해 TUN 인터페이스를 지원하는 Go UDP-중계 구현을 설명합니다. MD5 해시와 타임스탬프를 사용한 프로토콜이 상세히 설명되며, iperf3 벤치마크에서 925 Mbit/s가 제공됩니다. 중/시니어 네트워크 소프트웨어 개발자에게 적합합니다.

TUN 인터페이스를 사용한 Go UDP-중계 생성
Advertisement 728x90

Go와 TUN/TAP: YAML 설정으로 구현하는 UDP 릴레이

Go 개발자는 TUN 인터페이스와 UDP 터널과 호환되는 네트워크 릴레이를 구축할 수 있습니다. 이 아키텍처는 입구-출구 체인을 정의하기 위한 YAML 구성 파일을 사용합니다. 각 릴레이는 양방향 트래픽—TUN과 UDP 사이, 그리고 다시 돌아오는 방식—을 처리하며, 논리적 하드코딩 없이도 복잡한 라우팅이 가능합니다.

핵심은 모든 노드에 io.ReadWriteCloser 인터페이스를 사용하는 것입니다. 로직은 간단합니다: 입구에서 패킷을 읽고 출구로 전달한 후, 반대 방향의 흐름도 처리합니다.

YAML 구성 구조

설정 파일은 입구/출구 쌍의 배열로 릴레이를 정의합니다. 파라미터에는 인터페이스 유형, IP 주소 할당, NAT 규칙, 인증 정보 등이 포함됩니다.

Google AdInline article slot

예시: TUN → TUN 연결

relays:
  - ingress:
      type: tun
      name: tun10
      cidr: "10.0.0.2/24"
      peer: "10.0.0.1"
    egress:
      type: tun
      name: tun11
      cidr: "10.0.1.2/24"
      peer: "10.0.1.1"
      nat:
        forward:
          src: "10.0.1.1"
        backward:
          dst: "10.0.0.2"

UDP 릴레이를 만들려면 클라이언트 및 서버 엔드포인트를 추가하세요:

relays:
  - ingress:
      type: tun
      name: tun10
      cidr: "10.0.0.2/24"
      peer: "10.0.0.1"
    egress:
      type: udp
      dial: "localhost:4000"
      password: "pass"
  - ingress:
      type: udp
      listen: "localhost:4000"
      password: "pass"
    egress:
      type: tun
      name: tun11
      cidr: "10.0.1.2/24"
      peer: "10.0.1.1"
      nat:
        forward:
          src: "10.0.1.1"
        backward:
          dst: "10.0.0.2"

이 구성은 다음과 같은 체인을 생성합니다: tun10 → UDP 클라이언트 → UDP 서버 → tun11. 트래픽은 모두 로컬에서 처리됩니다.

Google AdInline article slot

UDP 터널 프로토콜

각 IP 패킷에 헤더가 추가됩니다: 4바이트 타임스탬프 (uint32 BigEndian) + 16바이트 MD5 해시. 해시는 비밀번호 + 타임스탬프 + 패킷의 처음 64바이트를 기반으로 계산됩니다.

수신 시 시스템은 다음을 확인합니다:

  • 패킷 크기 ≥ 헤더 크기 (20바이트)
  • 타임스탬프가 현재 시간 기준 10초 이내
  • 해시 일치 여부

패킷 해제 코드:

Google AdInline article slot
func (i *Ingress) Read(b []byte) (int, error) {
	 n, raddr, err := i.conn.ReadFrom(b)
	 if err != nil {
		 return 0, err
	 }

	 data, err := unpack(b[:n:n], i.pass)
	 if err != nil {
		 return 0, err
	 }

	 i.raddr = raddr
	 copy(b, data)
	 return len(data), nil
}

func unpack(packet []byte, pass string) ([]byte, error) {
	 if len(packet) < HeaderSize {
		 return nil, ErrSmallPacket
	 }

	 rtimestamp := binary.BigEndian.Uint32(packet[0:4])
	 rhash := [HashSize]byte(packet[4 : 4+HashSize])
	 payload := packet[HeaderSize:]

	 timestamp := uint32(time.Now().Unix())
	 if timestamp-rtimestamp > MaxTimeDiff && rtimestamp-timestamp > MaxTimeDiff {
		 return nil, ErrStalePacket
	 }

	 hash, err := calcHash(pass, payload, rtimestamp)
	 if err != nil {
		 return nil, fmt.Errorf("calc hash: %w", err)
	 }
	 if rhash != hash {
		 return nil, ErrWrongPass
	 }

	 return payload, nil
}

패킹은 대칭적입니다: calcHash + binary.BigEndian + 전송.

성능 테스트

tun10-tun11 간 iperf3를 사용한 로컬 테스트:

서버:

iperf3 -s -B 10.0.1.2

클라이언트:

iperf3 -c 10.0.1.2 -B 10.0.0.2

결과 (10초 기준):

| 구간 | 전송량 | 비트레이트 |

|----------|----------|---------|

| 0-1s | 113 MB | 941 Mbit/s |

| 1-2s | 109 MB | 921 Mbit/s |

| ... | ... | ... |

| 전체 | 1.08 GB | 925 Mbit/s (보내는 쪽) |

수신 쪽에서 약 922 Mbit/s. CPU: 릴레이 ~250% (M1), iperf3 ~10–30%. 배치 처리, 제로복사, SIMD 해싱 등을 통해 최적화 가능합니다.

분석을 위한 핵심 지표:

  • 처리량: 평균 925 Mbit/s
  • CPU 부하: 단일 스레드 고부하
  • 지연 시간: 10초 타임스탬프 윈도우 (로컬 테스트에 적합함)
  • 신뢰성: 해시 + 타임스탬프로 재전송 및 위조 공격 방지

핵심 요약

  • YAML 구성 덕분에 재컴파일 없이 TUN/UDP 혼용 가능
  • 프로토콜은 최소화됨: 20바이트 오버헤드, MD5 + 타임스탬프
  • 로컬에서 성능 ~900+ Mbit/s, 하지만 CPU 한계 존재
  • io.ReadWriteCloser 확장성: WireGuard, QUIC 등 쉽게 추가 가능
  • 구성 내 NAT 규칙으로 피어 투 피어 설정 간편화

— Editorial Team

Advertisement 728x90

다음 읽기