GRE (Generic Routing Encapsulation)

Linux 커널 GRE 서브시스템: RFC 2784/2890 프로토콜 구조, ip_gre/ip6_gre 커널 모듈(Kernel Module), GRE/GRETAP/ERSPAN 터널(Tunnel) 유형, 키·시퀀스·체크섬(Checksum) 옵션, Netfilter 연동, GSO/GRO 오프로드, PPTP, NVGRE, MTU/PMTUD 이슈, 성능 튜닝, 디버깅(Debugging) 종합 가이드. 커널 내부 데이터 경로, 핵심 자료구조/API, 운영 환경 튜닝 포인트와 장애 디버깅 절차까지 실무 관점으로 다룹니다.

전제 조건: IP 프로토콜, 라우팅(Routing), sk_buff 문서를 먼저 읽으세요. GRE 문제는 거의 항상 "외부 IP 경로", "GRE 헤더 옵션", "내부 페이로드(Payload)"의 세 층을 함께 봐야 풀립니다.
일상 비유: 이 개념은 택배 상자 안에 또 다른 상자를 넣어 보내는 것과 비슷합니다. 겉상자 주소는 underlay가 보고, 안쪽 상자 내용은 GRE 종단이 열어본 뒤 처리합니다. MTU 문제는 대개 상자를 한 겹 더 씌우면서 생깁니다.

핵심 요약

  • 외부 헤더 — underlay는 바깥 IP 헤더만 보고 전달하며, 기본 GRE는 IP 프로토콜 번호 47을 사용합니다.
  • GRE Key — 32비트 식별자로 다중화(Multiplexing)에 쓰이며, 암호화(Encryption)나 인증 기능은 아닙니다.
  • GRETAP — Protocol Type 0x6558(TEB)로 Ethernet 프레임 전체를 실어 L2 브리징을 제공합니다.
  • ERSPAN — GRE 위에 미러링 메타데이터를 추가해 원격 패킷(Packet) 캡처를 전송합니다.
  • 실무 핵심 — 성능과 장애는 대부분 MTU/PMTUD, ECMP 해시(Hash), NAT 통과, offload 지원 여부에서 갈립니다.

단계별 이해

  1. 캡슐화(Encapsulation) 층 분리
    문제를 outer IP, GRE 옵션, inner payload 중 어디에서 보는지 먼저 고정합니다.
  2. 링크 종류 선택
    L3면 gre, L2면 gretap, 미러링이면 erspan, 동적 제어면 external을 고릅니다.
  3. underlay 제약 확인
    NAT, ECMP, 방화벽(Firewall), 멀티패스 해시가 GRE에 우호적인지 확인합니다.
  4. 크기 계산
    추가 헤더만큼 MTU와 MSS를 줄이고, PMTUD/ICMP가 살아 있는지 검증합니다.
  5. 디버깅 표면 확정
    tcpdump, ip -d link, conntrack, tracefs 중 어디서 상태를 볼지 정합니다.
관련 표준: RFC 2784 (GRE), RFC 2890 (GRE Key & Sequence Number), RFC 1701 (원본 GRE), RFC 7676 (IPv6 over GRE), RFC 8086 (GRE-in-UDP), RFC 7348 (VXLAN), RFC 7637 (NVGRE) — 커널 GRE 구현이 따르는 터널링 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

GRE 개요

GRE(Generic Routing Encapsulation)는 임의의 네트워크 계층 프로토콜을 다른 네트워크 계층 프로토콜 위에 캡슐화하여 전송하는 터널링 프로토콜입니다. IP 프로토콜 번호 47을 사용하며, TCP/UDP와 달리 포트 개념이 없고 순수한 캡슐화 메커니즘으로 동작합니다.

특성GREIPSec (ESP)VXLANWireGuard
계층 IP 프로토콜 47 IP 프로토콜 50 UDP 4789 UDP 51820
캡슐화 대상 L2/L3 모두 가능 L3 전용 L2 프레임 L3 전용
암호화 없음 (IPSec 병용) 내장 (AES-GCM 등) 없음 (MACsec 병용) 내장 (ChaCha20)
오버헤드(Overhead) 4~16바이트 36~73바이트 50바이트 60바이트
NAT 통과 불가 (포트 없음) NAT-T (UDP 4500) 가능 (UDP) 가능 (UDP)
멀티캐스트 지원 제한적 지원 미지원
커널 모듈 ip_gre, ip6_gre xfrm, esp4/6 vxlan wireguard

GRE의 핵심 장점은 프로토콜 불가지론(protocol-agnostic)입니다. EtherType 필드를 통해 IPv4, IPv6, Ethernet, MPLS 등 다양한 페이로드를 캡슐화할 수 있으며, 라우팅 프로토콜의 멀티캐스트 패킷도 터널을 통해 전달할 수 있습니다.

링크 종류내부 페이로드대표 장점주의할 점
gre L3 패킷 오버헤드가 낮고 라우팅 프로토콜 터널링에 적합 NAT와 ECMP에 불리하며 포트 기반 식별이 없습니다
gretap Ethernet 프레임 브리지(Bridge)/OVS와 직접 연결되는 L2 오버레이(Overlay) 브로드캐스트 도메인이 그대로 확장되어 MTU와 BUM 트래픽 관리가 중요합니다
erspan 미러링된 Ethernet 프레임 원격 캡처, IDS, 포렌식 수집에 적합 일반 데이터 터널이 아니라 모니터링 전용으로 봐야 합니다
gre external 메타데이터 기반 TC, OVS, BPF가 패킷별로 dst/key를 결정할 수 있습니다 remote, local, key와 상호배타적이므로 제어 평면 설계가 먼저 필요합니다
gre + encap fou/gue L3 또는 L2 UDP 소스 포트로 ECMP 분산과 중간 장비 통과성이 좋아집니다 UDP 추가 헤더만큼 오버헤드가 늘고 중간 장비가 UDP 포트 정책을 가질 수 있습니다

GRE 패킷 구조

GRE 헤더 포맷 (RFC 2784 / RFC 2890)

RFC 2784는 원본 RFC 1701의 GRE 헤더를 단순화했으며, RFC 2890이 Key와 Sequence Number 확장을 추가했습니다. 커널 구현은 두 RFC를 모두 지원합니다.

GRE Header (RFC 2784 + RFC 2890) C K/S Reserved + Ver Protocol Type Checksum (optional) Reserved1 (optional) Key (optional, K=1) Sequence Number (optional, S=1) C: Checksum 존재 K: Key 존재 S: Sequence 존재 Ver: 0=GRE, 1=PPTP
/*
 * GRE 헤더 구조 (RFC 2784 + RFC 2890 확장)
 *
 *  0                   1                   2                   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |C| |K|S| Reserved0       | Ver |         Protocol Type         |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |      Checksum (optional)      |       Reserved1 (optional)    |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                         Key (optional)                        |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                    Sequence Number (optional)                 |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 * C (bit 0) : Checksum 존재 여부
 * K (bit 2) : Key 존재 여부
 * S (bit 3) : Sequence Number 존재 여부
 * Ver       : 버전 (0 = RFC 2784, 1 = PPTP용 Enhanced GRE)
 * Protocol Type : 캡슐화된 페이로드의 EtherType
 */

/* 커널 정의: include/net/gre.h */
struct gre_base_hdr {
    __be16 flags;    /* C, K, S 플래그 + Reserved + Ver */
    __be16 protocol; /* 페이로드 EtherType (예: 0x0800=IPv4, 0x86DD=IPv6, 0x6558=TEB) */
};
플래그 비트매크로(Macro)설명추가 헤더 크기
C (bit 0) GRE_CSUM (0x8000) Checksum + Reserved1 필드 존재 +4바이트
K (bit 2) GRE_KEY (0x2000) Key 필드 존재 (터널 식별/다중화) +4바이트
S (bit 3) GRE_SEQ (0x1000) Sequence Number 필드 존재 (순서 보장(Ordering)) +4바이트
Ver (bits 13-15) GRE_VERSION 0 = 표준 GRE, 1 = Enhanced GRE (PPTP)

전체 캡슐화 패킷 구조

외부 IP 헤더 Proto = 47 (GRE) GRE 헤더 4~16 바이트 내부 페이로드 (Inner Packet) IPv4 / IPv6 / Ethernet 프레임 등 GRE 헤더 상세 (최대 16바이트) Flags + Ver (2B) C|0|K|S|Rsv0|Ver Protocol Type (2B) 0x0800/0x86DD/0x6558 Checksum + Rsv1 (4B) Key (4B) Sequence Number (4B) — 점선 = 선택적 필드 주요 Protocol Type (EtherType) 값 0x0800 — IPv4 0x86DD — IPv6 0x6558 — TEB (Ethernet) 0x8847 — MPLS
Protocol Type 0x6558 (TEB): Transparent Ethernet Bridging — GRETAP 모드에서 사용됩니다. 내부 페이로드가 Ethernet 프레임 전체(MAC 헤더 포함)이므로 L2 브리징이 가능합니다.

커널 GRE 아키텍처

모듈 구조

Linux 커널의 GRE 구현은 여러 모듈로 계층화되어 있습니다:

커널 GRE 모듈 계층 구조 사용자 공간: iproute2 (ip tunnel / ip link) Netlink (RTM_NEWTUNNEL / RTM_NEWLINK) GRE 드라이버 계층 net/ipv4/ip_gre.c (GRE/GRETAP over IPv4) net/ipv6/ip6_gre.c (GRE/GRETAP over IPv6) net/ipv4/ip_tunnel.c (공통 터널 프레임워크) net/ipv4/gre_offload.c (GSO/GRO 콜백) GRE 공통 레이어 net/ipv4/gre_demux.c (프로토콜 디먹스) include/net/gre.h (헤더/플래그 정의) net/ipv4/ip_tunnel_core.c (iptunnel_xmit/rcv 공통) + include/net/ip_tunnels.h
커널 모듈소스 파일역할
gre net/ipv4/gre_demux.c IP 프로토콜 47 수신 디먹싱, GRE 핸들러(Handler) 등록 프레임워크
ip_gre net/ipv4/ip_gre.c IPv4 GRE/GRETAP/ERSPAN 터널 디바이스 구현
ip6_gre net/ipv6/ip6_gre.c IPv6 GRE/GRETAP/ERSPAN 터널 디바이스 구현
ip_tunnel net/ipv4/ip_tunnel.c IPv4 터널 공통 로직 (lookup, xmit, rcv, 통계)
net/ipv4/ip_tunnel_core.c iptunnel_xmit(), iptunnel_pull_header() 등 공통 함수
구현 위치 주의: ERSPAN은 별도 drivers/net/gre/ 계층이 아니라, 실제 송수신 경로는 ip_gre.c/ip6_gre.c를 타고 메타데이터 형식 정의만 include/net/erspan.h에 분리되어 있습니다.

핵심 자료구조: struct ip_tunnel

/* include/net/ip_tunnels.h — 현대 커널에서 중요한 필드만 발췌 */
struct ip_tunnel {
    struct ip_tunnel __rcu  *next;       /* 해시 체인의 다음 터널 */
    struct hlist_node       hash_node;   /* 해시 테이블 연결 */
    struct net_device       *dev;        /* 터널 net_device */
    struct net              *net;        /* 네트워크 네임스페이스 */

    unsigned long           err_time;    /* ICMP 에러 rate limit 타임스탬프 */
    int                     err_count;   /* ICMP 에러 카운트 */

    /* GRE/ERSPAN 전용 상태 */
    u32                     i_seqno;     /* 마지막으로 본 입력 시퀀스 번호 */
    atomic_t                o_seqno;     /* 마지막 출력 시퀀스 번호 */
    int                     tun_hlen;    /* 외부 IP + GRE 길이 */
    u8                      erspan_ver;  /* ERSPAN 버전 (0/1/2) */

    struct dst_cache        dst_cache;   /* underlay 라우팅 캐시 */
    struct ip_tunnel_parm   parms;       /* src, dst, key, flags, TTL/TOS */

    int                     encap_hlen;  /* FOU/GUE 같은 2차 UDP encap 길이 */
    int                     hlen;        /* tun_hlen + encap_hlen */
    struct ip_tunnel_encap  encap;       /* fou/gue/none 설정 */

    struct gro_cells        gro_cells;   /* GRO 처리용 per-CPU 셀 */
    __u32                   fwmark;      /* underlay 라우팅에 쓰는 fwmark */
    bool                    collect_md;  /* metadata 모드 (Collect Metadata) */
    bool                    ignore_df;   /* inner DF를 무시할지 여부 */
};

/* 터널 파라미터 — iproute2 'ip tunnel add' 시 설정 */
struct ip_tunnel_parm {
    char        name[IFNAMSIZ];  /* 터널 디바이스 이름 */
    int         link;              /* 물리 디바이스 ifindex (0=auto) */
    __be16      i_flags;           /* 입력 GRE 플래그 (GRE_KEY, GRE_CSUM, GRE_SEQ) */
    __be16      o_flags;           /* 출력 GRE 플래그 */
    __be32      i_key;             /* 입력 GRE 키 */
    __be32      o_key;             /* 출력 GRE 키 */
    struct iphdr iph;             /* 외부 IP 헤더 템플릿 (saddr, daddr, ttl, tos) */
};

커널 버전별로 필드 순서와 보조 멤버는 조금씩 달라지지만, 운영 관점에서 봐야 할 축은 거의 같습니다. 고정 파라미터는 parms, 패킷별 동적 정보는 collect_md, FOU/GUE 같은 2차 캡슐화는 encap/encap_hlen, PMTUD 예외 처리는 ignore_df, 순서 검사는 i_seqno/o_seqno가 담당합니다.

GRE 수신 경로 — 디먹싱

IP 레이어가 프로토콜 번호 47 패킷을 수신하면 gre_demux.c의 핸들러로 전달됩니다:

/* net/ipv4/gre_demux.c — GRE 수신 흐름 */

/* 1. IP 레이어에서 GRE 핸들러 호출 */
static int gre_rcv(struct sk_buff *skb)
{
    const struct gre_protocol *proto;
    u8 ver;

    /* GRE 헤더에서 버전 필드 추출 */
    ver = skb->data[1] & 0x7;
    if (ver >= GREPROTO_MAX)
        goto drop;

    /* 등록된 버전별 핸들러 호출
     * ver=0: ip_gre.c의 gre_rcv() → 표준 GRE
     * ver=1: pptp.c의 pptp_rcv() → PPTP (Enhanced GRE) */
    proto = rcu_dereference(gre_proto[ver]);
    if (!proto || !proto->handler)
        goto drop;

    return proto->handler(skb);
    ...
}

/* 2. ip_gre.c에서의 GRE 패킷 처리 */
static int gre_rcv(struct sk_buff *skb)
{
    struct tnl_ptk_info tpi;

    /* GRE 헤더 파싱: flags, key, seq, protocol type 추출 */
    if (gre_parse_header(skb, &tpi, NULL, htons(ETH_P_IP), 0) < 0)
        goto drop;

    /* ERSPAN 처리 (Protocol Type 0x88BE) */
    if (tpi.proto == htons(ETH_P_ERSPAN) ||
        tpi.proto == htons(ETH_P_ERSPAN2))
        return erspan_rcv(skb, &tpi, hlen);

    /* ip_tunnel_lookup()으로 매칭되는 터널 디바이스 탐색 */
    return ip_tunnel_rcv(tunnel, skb, &tpi, tun->hlen);
}

GRE 송신 경로

/* net/ipv4/ip_gre.c — GRE 패킷 송신 */
static netdev_tx_t gre_tap_xmit(struct sk_buff *skb,
                                  struct net_device *dev)
{
    struct ip_tunnel *tunnel = netdev_priv(dev);

    if (tunnel->collect_md) {
        /* Metadata 모드: skb의 tunnel_info에서 dst/key 추출
         * → OVS, TC tunnel_key 등 flow-based 터널에서 사용 */
        gre_fb_xmit(skb, dev, htons(ETH_P_TEB));
    } else {
        /* 일반 모드: ip_tunnel_parm에서 dst/key 사용 */
        gre_build_header(skb, tunnel->tun_hlen,
                         tunnel->parms.o_flags,
                         htons(ETH_P_TEB),
                         tunnel->parms.o_key,
                         htonl(tunnel->o_seqno++));

        /* ip_tunnel_xmit() → 외부 IP 헤더 추가 + 라우팅 + 송신 */
        ip_tunnel_xmit(skb, dev, &tunnel->parms.iph,
                        tunnel->parms.iph.protocol);
    }
    return NETDEV_TX_OK;
}

/* GRE 헤더 빌드 — 고속 경로 인라인 함수 */
static void gre_build_header(struct sk_buff *skb, int hdr_len,
                             __be16 flags, __be16 proto,
                             __be32 key, __be32 seq)
{
    struct gre_base_hdr *greh;

    skb_push(skb, hdr_len);         /* 헤더 공간 확보 */
    skb_reset_transport_header(skb); /* transport 헤더 = GRE 시작점 */

    greh = (struct gre_base_hdr *)skb_transport_header(skb);
    greh->flags = gre_tnl_flags_to_gre_flags(flags);
    greh->protocol = proto;

    /* 선택적 필드를 순서대로 채움 */
    if (flags & TUNNEL_CSUM)
        *ptr++ = 0;              /* 체크섬은 나중에 계산 */
    if (flags & TUNNEL_KEY)
        *ptr++ = key;
    if (flags & TUNNEL_SEQ)
        *ptr++ = seq;

    if (flags & TUNNEL_CSUM)
        /* GRE 체크섬 계산 (GRE 헤더 + 페이로드 전체) */
        *(__sum16 *)&greh[1] = gre_checksum(skb);
}

송수신 데이터 경로

운영자가 가장 자주 추적하는 흐름은 "어디서 캡슐화되고, 어디서 디캡슐레이션되며, 어느 단계에서 MTU·conntrack·offload가 개입하는가"입니다. 아래 흐름을 기준으로 장애 지점을 잘라내면 디버깅 속도가 크게 빨라집니다.

Linux GRE 송수신 데이터 경로 송신 경로 inner skb IP/Ethernet payload gre_build_header() Key / Seq / Csum ip_tunnel_xmit() route lookup, PMTUD, xmit underlay NIC GSO / checksum / qdisc 수신 경로 outer IP proto 47 또는 UDP+GRE gre_rcv() / gre_parse_header() flags, proto, key, seq 추출 ip_tunnel_lookup() remote/local/key 매칭 ip_tunnel_rcv() decap, gro_cells, inner stack Netfilter, PMTUD, qdisc, offload는 각 단계 사이에서 개입합니다

GRE 터널 유형

GRE 터널 (L3)

기본 GRE 터널은 L3(IP) 패킷을 캡슐화합니다. net_device 타입은 ARPHRD_IPGRE이며, L2 헤더 없이 IP 패킷을 직접 GRE로 감쌉니다.

# 기본 GRE 터널 생성 (L3 — point-to-point)
ip tunnel add gre1 mode gre \
    local 10.0.0.1 remote 10.0.0.2 \
    ttl 64

ip addr add 192.168.100.1/30 dev gre1
ip link set gre1 up

# 확인
ip tunnel show gre1
ip -d link show gre1

# 커널 내부: ip_gre.c → ipgre_newlink() → alloc_netdev()
# dev→type = ARPHRD_IPGRE
# dev→hard_header_len = 0 (L3 터널이므로 L2 헤더 없음)
# Protocol Type = 0x0800 (IPv4) 또는 0x86DD (IPv6)

GRETAP 터널 (L2)

GRETAP은 Ethernet 프레임 전체를 캡슐화하여 L2 브리징이 가능합니다. Protocol Type은 0x6558(TEB)이며, 브리지에 포트로 추가할 수 있습니다.

# GRETAP 터널 생성 (L2 — Ethernet 프레임 캡슐화)
ip link add gretap1 type gretap \
    local 10.0.0.1 remote 10.0.0.2 \
    ttl 64

ip link set gretap1 up

# L2 브리징에 참여
ip link add br0 type bridge
ip link set gretap1 master br0
ip link set eth1 master br0
ip link set br0 up

# → eth1과 gretap1이 같은 브로드캐스트 도메인을 공유
# 커널 내부: dev→type = ARPHRD_ETHER
# dev→hard_header_len = ETH_HLEN (14)
# Protocol Type = 0x6558 (Transparent Ethernet Bridging)
특성GRE (L3)GRETAP (L2)
캡슐화 대상 IP 패킷 (L3) Ethernet 프레임 (L2)
ARPHRD ARPHRD_IPGRE ARPHRD_ETHER
Protocol Type 0x0800 (IPv4) / 0x86DD (IPv6) 0x6558 (TEB)
브리지 포트 불가 가능
MAC 주소 없음 (point-to-point) 있음 (자동 생성)
ARP 불필요 (NOARP) 정상 동작
오버헤드 외부IP(20) + GRE(4~16) 외부IP(20) + GRE(4~16) + 내부ETH(14)
대표 사용 사례 site-to-site VPN, 라우팅 프로토콜 터널링 L2 VPN, 원격 브리징, OVS 오버레이

IPv6 GRE (ip6_gre)

IPv6를 외부 전송 프로토콜로 사용하는 GRE 터널입니다. ip6_gre.c 모듈이 처리하며, ip6gre/ip6gretap 두 가지 모드를 지원합니다.

# IPv6 GRE 터널 (L3)
ip link add ip6gre1 type ip6gre \
    local 2001:db8::1 remote 2001:db8::2

# IPv6 GRETAP 터널 (L2)
ip link add ip6gretap1 type ip6gretap \
    local 2001:db8::1 remote 2001:db8::2

# 커널: net/ipv6/ip6_gre.c → ip6gre_newlink()
# 외부 IP 헤더가 IPv6 (40바이트)이므로 오버헤드 증가

ERSPAN (Encapsulated Remote SPAN)

ERSPAN은 GRE를 이용해 미러링된 트래픽을 원격으로 전송하는 프로토콜입니다. 네트워크 모니터링과 IDS/IPS 배치에 주로 사용됩니다.

ERSPAN 버전Protocol Type추가 헤더특징
Type I 0x88BE 없음 추가 ERSPAN 메타데이터 없이 원시 미러링 프레임 전달
Type II (v1) 0x88BE 8바이트 ERSPAN 헤더 Session ID, VLAN, COS, 포트 인덱스
Type III (v2) 0x22EB 12바이트 ERSPAN 헤더 + 선택적 서브헤더 타임스탬프 (100μs 해상도), BSO, FT, SGT
# ERSPAN Type II 터널 (미러링 소스)
ip link add erspan1 type erspan \
    local 10.0.0.1 remote 10.0.0.2 \
    seq key 100 \
    erspan_ver 1 erspan 100    # session ID = 100

# ERSPAN Type III 터널
ip link add erspan2 type erspan \
    local 10.0.0.1 remote 10.0.0.2 \
    seq key 200 \
    erspan_ver 2 erspan_dir ingress erspan_hwid 0x7

ip link set erspan1 up

# tc mirror 액션으로 트래픽 미러링 연결
tc qdisc add dev eth0 ingress
tc filter add dev eth0 ingress protocol all \
    matchall action mirred egress mirror dev erspan1

# 커널 내부: ip_gre.c/ip6_gre.c 경로에서 ERSPAN 헤더를 추가
# Linux iproute2 UAPI는 ERSPAN 링크 생성 시 seq + key 인자를 함께 받습니다
Linux UAPI 포인트: ip link help erspan 기준으로 erspan/ip6erspan 링크는 seq, key, erspan_ver 조합을 사용합니다. Type I 자체는 추가 ERSPAN 헤더가 없지만, Linux 실무에서는 Type II/III 링크를 직접 생성하는 경우가 훨씬 많습니다.
/* include/net/erspan.h — ERSPAN 헤더 구조체 */
struct erspan_base_hdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u8  vlan_upper:4,
          ver:4;
    __u8  vlan:8;
    __u8  session_id_upper:2,
          t:1,
          en:2,
          cos:3;
    __u8  session_id:8;
#else
    ...
#endif
};

/* ERSPAN Type III 확장 */
struct erspan_md2 {
    __be32 timestamp;  /* 100μs granularity */
    __be16 sgt;        /* Security Group Tag */
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u8  hwid_upper:2,
          ft:5,
          p:1;
    __u8  o:1,
          gra:2,
          dir:1,
          hwid:4;
#else
    ...
#endif
};

GRE 키, 시퀀스 번호, 체크섬

GRE Key를 이용한 터널 다중화

GRE Key는 동일한 src/dst IP 쌍에서 여러 논리 터널을 다중화하는 32비트 식별자입니다. 커널은 터널 lookup 시 (remote_ip, local_ip, key) 3-tuple로 매칭합니다.

# 같은 엔드포인트에 서로 다른 키로 다중 터널
ip link add gre-a type gre \
    local 10.0.0.1 remote 10.0.0.2 key 100
ip link add gre-b type gre \
    local 10.0.0.1 remote 10.0.0.2 key 200

# 입력/출력 키를 별도로 설정 (비대칭 키)
ip link add gre-asym type gre \
    local 10.0.0.1 remote 10.0.0.2 \
    ikey 100 okey 200

# 커널 내부: ip_tunnel_lookup()
# → hash(remote, local, key)로 O(1) 탐색
# fallback: wildcard remote (0.0.0.0) → 다중 피어 수용 가능
/* net/ipv4/ip_tunnel.c — 터널 lookup 로직 */
struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
    int link, __be16 flags, __be32 remote, __be32 local, __be32 key)
{
    struct ip_tunnel *t, *cand = NULL;
    struct hlist_head *head;
    unsigned int hash;

    /* 1차: (remote, local, key) 정확 매칭 */
    hash = ip_tunnel_hash(key, remote);
    head = &itn->tunnels[hash];
    hlist_for_each_entry_rcu(t, head, hash_node) {
        if (local == t->parms.iph.saddr &&
            remote == t->parms.iph.daddr &&
            key == t->parms.i_key)
            return t;  /* 정확 매칭 */
    }

    /* 2차: wildcard remote (0.0.0.0) 매칭 */
    hash = ip_tunnel_hash(key, 0);
    head = &itn->tunnels[hash];
    hlist_for_each_entry_rcu(t, head, hash_node) {
        if (local == t->parms.iph.saddr &&
            key == t->parms.i_key)
            cand = t;
    }

    /* 3차: fallback 터널 (gre0 등) */
    ...
    return cand;
}

시퀀스 번호

GRE 시퀀스 번호는 RFC 2890에서 "신뢰성은 없지만 순서 있는 전달"을 위해 정의됐습니다. Linux에서도 입력 시퀀스 검사는 순서가 어긋난 패킷을 드롭하므로, 재정렬이 조금이라도 발생할 수 있는 underlay에서는 매우 조심해서 써야 합니다.

# 출력 쪽에만 시퀀스 번호를 붙이는 예시
ip link add gre-txseq type gre \
    local 10.0.0.1 remote 10.0.0.2 oseq

# 수신 쪽에서 시퀀스 검증을 요구하는 예시
ip link add gre-rxseq type gre \
    local 10.0.0.2 remote 10.0.0.1 iseq

# 주의:
# 1. ip-tunnel(8)은 결합 옵션인 'seq'가 제대로 동작하지 않으므로 사용하지 말라고 경고합니다.
# 2. ECMP, LAG, RSS, 재전송 경로가 있는 underlay에서는 iseq를 쉽게 깨뜨립니다.
/* 수신 측 시퀀스 번호 검증 */
if (flags & TUNNEL_SEQ) {
    if (ntohl(tpi->seq) < tunnel->i_seqno) {
        /* 이전 시퀀스 → 드롭 (replay 또는 reordering) */
        tunnel->dev->stats.rx_fifo_errors++;
        tunnel->dev->stats.rx_errors++;
        goto drop;
    }
    tunnel->i_seqno = ntohl(tpi->seq) + 1;
}

체크섬

GRE 체크섬은 GRE 헤더와 캡슐화된 페이로드 전체에 대한 무결성(Integrity) 검증입니다. 외부 IP 헤더의 체크섬과는 별도로 동작합니다.

# 체크섬 활성화
ip link add gre1 type gre \
    local 10.0.0.1 remote 10.0.0.2 csum

# 입력/출력 별도 설정
ip link add gre1 type gre \
    local 10.0.0.1 remote 10.0.0.2 \
    icsum ocsum    # 양방향 체크섬
성능 주의: GRE 체크섬을 활성화하면 소프트웨어 체크섬 계산 오버헤드가 발생합니다. 하드웨어 오프로드(tx-gre-csum-segmentation)를 지원하는 NIC에서는 영향이 적지만, 소프트웨어 전용 환경에서는 처리량(Throughput)이 10~20% 감소할 수 있습니다. 내부 페이로드가 이미 TCP/UDP 체크섬으로 보호되므로 대부분의 환경에서 GRE 체크섬은 불필요합니다.

고급 설정

Collect Metadata 모드 (Flow-Based Tunneling)

Collect Metadata 모드(external)는 터널 파라미터를 디바이스에 고정하지 않고, 각 패킷의 메타데이터에서 동적으로 추출합니다. OVS(Open vSwitch), TC tunnel_key 액션, BPF 프로그램에서 활용합니다.

# Collect Metadata 모드 GRE 디바이스
ip link add gre-md type gre external

# TC를 이용한 flow-based 터널링
tc qdisc add dev gre-md ingress
tc filter add dev gre-md ingress \
    flower enc_dst_ip 10.0.0.2 enc_key_id 100 \
    action tunnel_key unset \
    action mirred egress redirect dev eth0

# 송신 방향: 패킷에 터널 메타데이터 설정
tc qdisc add dev eth0 ingress
tc filter add dev eth0 ingress \
    flower dst_ip 192.168.0.0/24 \
    action tunnel_key set id 100 dst_ip 10.0.0.2 \
    action mirred egress redirect dev gre-md
/* 커널 내부: collect_md 모드 송신 */
static netdev_tx_t gre_fb_xmit(struct sk_buff *skb,
                                 struct net_device *dev,
                                 __be16 proto)
{
    struct ip_tunnel_info *tun_info;

    /* skb에 연결된 tunnel metadata 추출 */
    tun_info = skb_tunnel_info(skb);
    if (!tun_info || !(tun_info->mode & IP_TUNNEL_INFO_TX))
        goto err_free_skb;

    key = &tun_info->key;
    /* key→u.ipv4.dst  → 외부 목적지 IP
     * key→tun_id      → GRE Key
     * key→tun_flags   → GRE 플래그 (CSUM, KEY, SEQ) */
    ...
}
external 모드 제약: iproute2 매뉴얼 기준으로 externalremote, local, key, csum, seq 같은 고정 옵션과 상호배타적입니다. 즉 "고정 목적지 터널"이 아니라 "외부 제어 평면이 패킷별 메타데이터를 채우는 장치"로 이해해야 맞습니다.

다중점 GRE (mGRE)

원격 주소를 지정하지 않은 GRE 터널은 여러 피어와 동적으로 통신할 수 있습니다. NHRP(Next Hop Resolution Protocol)와 결합하면 DMVPN(Dynamic Multipoint VPN) 구현이 가능합니다.

# mGRE 터널 (remote 미지정)
ip tunnel add mgre0 mode gre \
    local 10.0.0.1 key 1000 \
    ttl 64

# remote를 지정하지 않았으므로 wildcard 터널
# 패킷의 목적지 IP를 기반으로 NHRP가 next-hop 결정
# → 라우팅 테이블 또는 NHRP 캐시에서 외부 목적지 결정

ip addr add 172.16.0.1/24 dev mgre0
ip link set mgre0 up

# NHRP 데몬 (OpenNHRP 또는 FRR)이 필요
# Cisco DMVPN과 상호 운용 가능

피어 생존 감지와 제어 평면

일부 벤더 장비 문서에는 GRE keepalive가 자주 등장하지만, 현재 Linux의 ip tunnel/ip link GRE UAPI에는 그런 설정 노브가 없습니다. 따라서 피어 생존 감지는 GRE 자체가 아니라 GRE 위나 아래에서 따로 설계해야 합니다.

방법배치 위치장점주의점
BFD GRE 위 또는 underlay 탐지 시간이 짧고 라우팅과 연계하기 쉽습니다 추가 데몬과 라우팅 스택 연동이 필요합니다
OSPF/BGP hello GRE 위 제어 평면과 데이터 경로를 함께 검증합니다 단순 L2 브리징만 쓰는 GRETAP에는 직접 적용하기 어렵습니다
ICMP/HTTP 헬스체크 GRE 위 구성은 쉽고 애플리케이션까지 검증 가능합니다 장애 감지 속도와 오탐 관리가 약합니다
underlay BFD/ICMP 외부 IP 경로 터널 바깥 경로 불안정을 빨리 감지합니다 GRE 종단 로직이나 내부 라우팅 상태까지는 보장하지 않습니다
운영 권장: 순수 GRE는 라우팅 프로토콜 hello/BFD와 함께 쓰고, GRE-in-UDP는 RFC 8086 취지대로 UDP 소스 포트를 엔트로피 용도로 유지하는 편이 낫습니다. 즉 Linux에서는 "GRE 자체 keepalive"를 찾기보다, 상위 제어 평면과 underlay 헬스체크를 조합하는 방식이 현실적입니다.

GRE와 Netfilter/conntrack

nf_conntrack_proto_gre

GRE는 TCP/UDP와 달리 포트가 없으므로, conntrack은 GRE Key를 포트 대신 사용하여 연결을 추적합니다. PPTP helper (nf_conntrack_pptp)는 GRE 콜 ID를 이용한 NAT를 지원합니다.

/* net/netfilter/nf_conntrack_proto_gre.c
 *
 * GRE conntrack 튜플:
 *   src: (src_ip, gre_key_or_call_id)
 *   dst: (dst_ip, gre_key_or_call_id)
 *
 * GRE v0 (표준): Key 필드를 식별자로 사용
 * GRE v1 (PPTP): Call ID를 식별자로 사용
 */

/* conntrack 조회 */
conntrack -L -p gre

# 출력 예:
# gre      47 src=10.0.0.1 dst=10.0.0.2 srckey=0x00000064 dstkey=0x00000064
#              src=10.0.0.2 dst=10.0.0.1 srckey=0x00000064 dstkey=0x00000064

nftables/iptables GRE 필터링

# nftables: GRE 프로토콜 필터링
nft add rule inet filter forward \
    ip protocol gre accept

# GRE 키 기반 필터링 (K=1, C=0, S=0 으로 헤더 길이를 고정한 환경만)
nft add rule inet filter forward \
    ip protocol gre \
    @th,32,32 0x00000064 accept

# iptables: GRE 프로토콜 허용
iptables -A FORWARD -p gre -j ACCEPT

# PPTP helper 로드 (GRE v1 NAT 지원)
modprobe nf_conntrack_pptp
modprobe nf_nat_pptp

# PPTP 서버로의 DNAT
iptables -t nat -A PREROUTING -p tcp --dport 1723 \
    -j DNAT --to-destination 192.168.1.100
# → nf_conntrack_pptp가 GRE 세션을 자동 추적하여 NAT 수행
raw 오프셋(Offset) 매칭 주의: nftables는 고수준 gre key 파서를 제공하지 않으므로, Key 매칭은 결국 raw 오프셋에 의존합니다. 체크섬(C)이나 시퀀스(S)가 켜지면 Key 오프셋이 달라지므로, 운영 환경에서 GRE 옵션 조합을 고정하지 않았다면 인터페이스 단위 정책이나 TC/OVS 메타데이터 기반 정책이 더 안전합니다.
GRE NAT 한계: 표준 GRE(v0)는 TCP/UDP처럼 포트 기반 NAPT 식별자가 없어서 일반적인 대칭 NAT 환경과 궁합이 나쁩니다. 특히 동일 공인 주소 뒤에서 여러 GRE 종단을 다중화하기 어렵고, GRE Key가 있어도 중간 NAT 장비가 이를 이해하지 못하면 구분이 되지 않습니다. 이 제한을 피하려면 GRE-in-UDP(RFC 8086)나 IPsec NAT-T 같은 포트 기반 캡슐화를 고려하세요.

GRE 하드웨어/소프트웨어 오프로드

GSO (Generic Segmentation Offload)

GSO는 GRE 터널 내부의 대형 TCP 세그먼트를 지연(Latency) 분할하여 처리량을 극대화합니다:

/* GSO 유형: include/linux/skbuff.h */
SKB_GSO_GRE         /* GRE 터널의 GSO — 내부 IP 분할 */
SKB_GSO_GRE_CSUM    /* GRE 체크섬 포함 GSO — HW가 GRE csum도 처리 */

/* NIC feature 플래그 */
NETIF_F_GSO_GRE         /* tx-gre-segmentation */
NETIF_F_GSO_GRE_CSUM    /* tx-gre-csum-segmentation */
# NIC의 GRE 오프로드 기능 확인
ethtool -k eth0 | grep gre
# tx-gre-segmentation: on
# tx-gre-csum-segmentation: on

# GRE 오프로드 비활성화 (디버깅용)
ethtool -K eth0 tx-gre-segmentation off
ethtool -K eth0 tx-gre-csum-segmentation off

GRO (Generic Receive Offload)

수신 측에서는 GRO가 여러 GRE 패킷을 하나의 큰 패킷으로 병합하여 프로토콜 스택 처리 효율을 높입니다:

/* net/ipv4/gre_offload.c — GRE GRO 콜백 */
static struct sk_buff *gre_gro_receive(
    struct list_head *head, struct sk_buff *skb)
{
    /* GRE 헤더 파싱 후, 동일한 flow (src/dst/key)의 패킷을 병합
     * → 내부 TCP 세그먼트를 하나의 큰 skb로 결합
     * → 프로토콜 스택을 한 번만 타므로 CPU 절약 */
    ...
}

/* ip_gre.c에서 GRO 셀 초기화 */
gro_cells_init(&tunnel->gro_cells, dev);
/* → per-CPU GRO 처리로 멀티코어 확장성 확보 */
오프로드 기능방향ethtool 플래그효과
GSO (GRE) TX tx-gre-segmentation 64KB 청크를 MTU 크기로 지연 분할
GSO (GRE+csum) TX tx-gre-csum-segmentation GSO + HW GRE 체크섬 계산
GRO RX rx-gro-hw (NIC별) 수신 패킷 병합으로 인터럽트(Interrupt)/스택 처리 감소
RX Checksum RX rx-checksum 내부/외부 체크섬 HW 검증

MTU와 단편화 (Path MTU Discovery)

GRE 캡슐화는 패킷 크기를 증가시키므로 MTU 관리가 매우 중요합니다. 잘못된 MTU 설정은 블랙홀, 성능 저하, 연결 실패의 주요 원인입니다.

MTU 계산

GRE 터널 MTU 계산 (물리 MTU = 1500 bytes) 터널 유형 헤더 오버헤드 구성 터널 MTU GRE (기본) 옵션 없음 IP 헤더 20 bytes GRE 4 bytes = 24 bytes 1476 bytes GRE + Key IP 20 GRE+Key 8 = 28 bytes 1472 bytes GRE+Key +Seq+Csum IP 20 GRE Full 16 = 36 bytes 1464 bytes GRETAP + Key (L2 터널) IP 20 GRE 8 내부 ETH 14 bytes = 42 bytes 1458 bytes IPv6 GRE: IP 헤더 40 bytes (+20 bytes 추가 오버헤드) 설정: ip tunnel add gre0 ... ttl 64 → MTU 자동 조정 또는 mtu 1476 명시적 설정
터널 유형오버헤드터널 MTU (물리 1500)
GRE (기본)24바이트1476
GRE + Key28바이트1472
GRE + Key + Seq32바이트1468
GRE + Key + Seq + Csum36바이트1464
GRETAP (기본)38바이트1462
GRETAP + Key42바이트1458
IPv6 GRE (기본)44바이트1456
ERSPAN Type II50바이트1450
ERSPAN Type III54바이트1446

Path MTU Discovery

# 터널 MTU 명시 설정
ip link set gre1 mtu 1400

# PMTUD 관련 커널 동작:
# 1. 외부 IP 헤더에 DF(Don't Fragment) 비트 설정 (기본값)
# 2. 중간 라우터가 ICMP "Fragmentation Needed" 반환
# 3. 커널이 터널 MTU를 동적으로 조정

# DF 비트 정책 설정
ip tunnel change gre1 pmtudisc     # DF 비트 설정 (기본값 — PMTUD 활성화)
ip tunnel change gre1 nopmtudisc   # DF 비트 해제 (단편화 허용)
ip link set dev gre1 type gre ignore-df # inner DF를 무시하고 외부 조각화를 허용

# MSS clamping으로 블랙홀 방지
iptables -t mangle -A FORWARD -o gre1 \
    -p tcp --tcp-flags SYN,RST SYN \
    -j TCPMSS --clamp-mss-to-pmtu

# nftables 동일 설정
nft add rule inet mangle forward \
    oifname "gre1" tcp flags syn / syn,rst \
    tcp option maxseg size set rt mtu
/* net/ipv4/ip_tunnel.c — 터널 송신 시 MTU 처리 */
void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
                     const struct iphdr *tnl_params, u8 protocol)
{
    ...
    /* DF 비트가 설정되어 있고 패킷이 MTU를 초과하면 */
    if (tnl_params->frag_off & htons(IP_DF)) {
        if (skb->len > mtu) {
            /* ICMP "Fragmentation Needed" 발신자에게 전송 */
            icmp_ndo_send(skb, ICMP_DEST_UNREACH,
                          ICMP_FRAG_NEEDED,
                          htonl(mtu));
            goto tx_error;
        }
    }
    ...
}
PMTUD 블랙홀: 중간 방화벽이 ICMP "Fragmentation Needed" 메시지를 차단하면 PMTUD가 실패하여 TCP 연결이 끊깁니다. 해결 방법: (1) MSS clamping 적용, (2) 터널 MTU를 보수적으로 설정 (예: 1400), (3) nopmtudisc로 단편화 허용 (성능 감소).
ignore-df는 마지막 수단: iproute2는 GRE/GRETAP에 [no]ignore-df를 제공합니다. 이 옵션은 inner 패킷의 DF를 무시해 조각화를 강제로 허용하므로, PMTUD가 완전히 깨진 오래된 경로에서는 임시 처방이 될 수 있지만 CPU 사용량과 재조립 비용이 늘고 중간 장비 호환성도 나빠질 수 있습니다.

GRE 변형 프로토콜

PPTP (Point-to-Point Tunneling Protocol)

PPTP는 Enhanced GRE(버전 1)를 사용하는 VPN 프로토콜입니다. TCP 1723 포트로 제어 채널을 설정하고, GRE v1으로 데이터를 전송합니다.

설명 요약:
  • Enhanced GRE (Version 1) — PPTP 전용
  • 0 1 2 3
  • 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  • +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • |C|R|K|S|s|Recur|A| Flags | Ver | Protocol Type (880B) |
  • +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • | Key (Payload Length) | Key (Call ID) |
  • +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • | Sequence Number (optional) |
  • +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • | Acknowledgment Number (optional) |
  • +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • Ver = 1 (Enhanced GRE)
  • K = 1 항상 (Call ID가 Key 역할)
  • Protocol Type = 0x880B (PPP)
  • A 비트: Acknowledgment 필드 존재
  • 커널 모듈: drivers/net/ppp/pptp.c
  • conntrack: nf_conntrack_pptp, nf_nat_pptp
# PPTP 관련 커널 모듈
modprobe ppp_generic
modprobe pptp
modprobe nf_conntrack_pptp   # GRE v1 conntrack
modprobe nf_nat_pptp         # PPTP NAT helper

# PPTP 서버는 pptpd, 클라이언트는 pptp-linux/NetworkManager 사용
# 보안 주의: PPTP의 MS-CHAPv2 인증은 취약 — IPSec/WireGuard 권장

NVGRE (Network Virtualization using GRE)

NVGRE(RFC 7637)는 데이터 센터에서 GRE Key의 상위 24비트를 VSID(Virtual Subnet ID)로 사용하여 멀티테넌트 네트워크를 구현합니다.

NVGRE 패킷 구조 외부 IP (20 B) GRE (K=1) (8 B) 내부 Ethernet (14 B) 내부 IP + 데이터 (가변) GRE Key (32-bit): VSID (24비트) 가상 서브넷 식별자 FlowID (8비트) ECMP 엔트로피 VSID: 최대 16M 가상 네트워크 (VLAN 4096 한계 극복) FlowID: ECMP 로드 밸런싱용 엔트로피 소스 Protocol Type = 0x6558 (TEB) — GRETAP과 동일
# Linux에서 NVGRE 시뮬레이션 (GRETAP + Key)
ip link add nvgre1 type gretap \
    local 10.0.0.1 remote 10.0.0.2 \
    key 0x000064ff    # VSID=100 (0x64), FlowID=0xff

# 실제 NVGRE 구현은 OVS 또는 하드웨어 VTEP에서 주로 사용

GRE-in-UDP (RFC 8086)

GRE를 UDP로 감싸서 NAT 통과와 ECMP 로드 밸런싱 문제를 해결합니다:

GRE-in-UDP 캡슐화 (RFC 8086) 외부 IP (20 B) UDP (8 B) GRE (4~16 B) 내부 페이로드 (가변) UDP 소스 포트: flow entropy (ECMP 분산용 해시) UDP 목적지 포트: 4754 (고정) 장점: - NAT 통과 가능 (UDP 포트 활용) - ECMP 로드 밸런싱 (UDP 소스 포트 해시) - 기존 GRE 인프라와 호환 커널: FOU(Foo-over-UDP) 프레임워크 활용
# FOU (Foo-over-UDP) 수신 포트 설정
modprobe fou
ip fou add port 4754 ipproto 47  # GRE-in-UDP 수신

# GRE 터널 생성 + FOU 캡슐화
ip link add gre-fou type gre \
    local 10.0.0.1 remote 10.0.0.2 \
    encap fou encap-sport auto encap-dport 4754

ip link set gre-fou up
RFC 8086 핵심: UDP 목적지 포트는 4754(GRE-in-UDP) 또는 4755(GRE-UDP-DTLS)를 쓰고, UDP 소스 포트는 엔트로피 용도로 쓰는 것이 권장됩니다. RFC는 가능한 경우 임시 포트 범위(49152~65535)에서 엔트로피 값을 선택하고, IPv6 underlay에서는 flow label에도 같은 엔트로피를 반영하라고 권고합니다.

실전 활용 시나리오

사이트 간 VPN (GRE + IPSec)

GRE 단독으로는 암호화가 없으므로, 일반적으로 IPSec과 결합하여 사용합니다. GRE가 터널링을, IPSec이 암호화를 담당합니다.

# 1. GRE 터널 생성
ip tunnel add gre-vpn mode gre \
    local 203.0.113.1 remote 198.51.100.1 \
    ttl 64 key 1000

ip addr add 172.16.0.1/30 dev gre-vpn
ip link set gre-vpn up
ip route add 192.168.2.0/24 dev gre-vpn

# 2. IPSec으로 GRE 트래픽 암호화 (xfrm)
ip xfrm state add \
    src 203.0.113.1 dst 198.51.100.1 \
    proto esp spi 0x1000 mode transport \
    enc "aes" 0x$(openssl rand -hex 16) \
    auth "hmac(sha256)" 0x$(openssl rand -hex 32)

ip xfrm state add \
    src 198.51.100.1 dst 203.0.113.1 \
    proto esp spi 0x1001 mode transport \
    enc "aes" 0x$(openssl rand -hex 16) \
    auth "hmac(sha256)" 0x$(openssl rand -hex 32)

# 3. IPSec 정책: GRE 트래픽(프로토콜 47)만 암호화
ip xfrm policy add \
    src 203.0.113.1 dst 198.51.100.1 \
    proto gre dir out \
    tmpl src 203.0.113.1 dst 198.51.100.1 \
    proto esp mode transport

ip xfrm policy add \
    src 198.51.100.1 dst 203.0.113.1 \
    proto gre dir in \
    tmpl src 198.51.100.1 dst 203.0.113.1 \
    proto esp mode transport

# 결과 패킷 구조:
# [외부 IP] [ESP] [GRE] [내부 IP] [페이로드]
# → GRE 터널 전체가 ESP로 암호화됨

멀티캐스트 라우팅 프로토콜 터널링

GRE의 핵심 사용 사례 중 하나는 OSPF, RIP 등의 멀티캐스트/브로드캐스트 라우팅 프로토콜을 인터넷을 통해 전달하는 것입니다:

# OSPF 멀티캐스트를 GRE로 전달
ip tunnel add gre-ospf mode gre \
    local 10.0.0.1 remote 10.0.0.2 \
    ttl 64

ip addr add 172.16.0.1/30 dev gre-ospf
ip link set gre-ospf up
ip link set gre-ospf multicast on    # 멀티캐스트 활성화

# FRRouting OSPF 설정에서 GRE 인터페이스 포함
# interface gre-ospf
#   ip ospf area 0.0.0.0
#   ip ospf network point-to-point

OVS (Open vSwitch) GRE 오버레이

# OVS에서 GRE 포트 추가
ovs-vsctl add-br br-int
ovs-vsctl add-port br-int gre0 -- \
    set interface gre0 type=gre \
    options:remote_ip=10.0.0.2 \
    options:key=flow    # flow-based key (OVS가 동적 결정)

# OVS는 내부적으로 collect_md 모드의 GRE 디바이스를 사용
# → OpenFlow 규칙으로 터널 엔드포인트와 키를 동적 지정

네트워크 네임스페이스(Namespace) 간 GRE

# 네임스페이스 생성
ip netns add ns1
ip netns add ns2

# veth 쌍으로 네임스페이스 연결
ip link add veth1 type veth peer name veth2
ip link set veth1 netns ns1
ip link set veth2 netns ns2

ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth1
ip netns exec ns1 ip link set veth1 up
ip netns exec ns2 ip addr add 10.0.0.2/24 dev veth2
ip netns exec ns2 ip link set veth2 up

# 각 네임스페이스에서 GRE 터널 생성
ip netns exec ns1 ip tunnel add gre1 mode gre \
    local 10.0.0.1 remote 10.0.0.2 key 42
ip netns exec ns1 ip addr add 172.16.0.1/30 dev gre1
ip netns exec ns1 ip link set gre1 up

ip netns exec ns2 ip tunnel add gre1 mode gre \
    local 10.0.0.2 remote 10.0.0.1 key 42
ip netns exec ns2 ip addr add 172.16.0.2/30 dev gre1
ip netns exec ns2 ip link set gre1 up

# 테스트
ip netns exec ns1 ping -c 3 172.16.0.2

성능 튜닝

성능 최적화 체크리스트

항목기본값권장 설정효과
GSO/GRO on (NIC 지원 시) on 유지 처리량 2~5배 향상
GRE 체크섬 off off 유지 체크섬 계산 오버헤드 제거
시퀀스 번호 off off (ECMP 환경) reorder 드롭 방지
MTU 자동 계산 명시 설정 + MSS clamp 단편화/블랙홀 방지
TTL inherit (내부 TTL) 64 고정 TTL 소진 루프 방지
txqueuelen 1000 5000~10000 (고대역폭) 큐 오버플로 감소
RPS/RFS off on (멀티코어) 터널 수신 CPU 분산
nopmtudisc off (DF 설정) off 유지 + MSS clamp 최적 MTU 자동 탐색
# 성능 최적화 적용 예시
ip link set gre1 mtu 1400                       # 보수적 MTU
ip link set gre1 txqueuelen 5000                # 큐 확장
ethtool -K eth0 tx-gre-segmentation on          # GSO 활성화

# MSS clamping (PMTUD 블랙홀 방지)
nft add rule inet mangle forward \
    oifname "gre1" tcp flags syn / syn,rst \
    tcp option maxseg size set rt mtu

# RPS 활성화 (터널 수신 CPU 분산)
echo ff > /sys/class/net/gre1/queues/rx-0/rps_cpus

# conntrack 비활성화 (순수 L3 터널, NAT 불필요 시)
nft add rule inet raw prerouting ip protocol gre notrack
nft add rule inet raw output ip protocol gre notrack

ECMP 해시와 내부 헤더 활용

순수 GRE는 underlay 관점에서 포트가 없기 때문에 많은 라우터가 outer IP 주소와 프로토콜 47만으로 해시를 계산합니다. 그 결과 대역폭(Bandwidth)이 충분한 멀티패스 underlay에서도 한 경로로만 몰리는 path polarization이 흔합니다.

ECMP 해시: 순수 GRE와 GRE-in-UDP의 차이 순수 GRE Outer IP src/dst + proto 47 ECMP 해시 엔트로피 부족 Path A Path B Path C 대개 한 flow가 같은 path에 고정되기 쉽습니다 GRE-in-UDP 또는 Linux inner-hash 활용 Outer IP + UDP src port = entropy ECMP 해시 경로 분산 Path A Path B Path C UDP 엔트로피나 inner header 해시로 분산 폭을 넓힙니다

Linux host 자체가 multipath next-hop을 선택하는 경우에는 fib_multipath_hash_policyfib_multipath_hash_fields로 inner header까지 해시에 반영할 수 있습니다. 다만 이것은 이 Linux 호스트의 라우팅 결정에만 영향을 주며, 중간 하드웨어 라우터의 해시 방식은 바꾸지 못합니다.

# Linux host의 multipath 라우팅이 inner L3까지 보도록 설정
sysctl -w net.ipv4.fib_multipath_hash_policy=2

# 사용자 정의 해시: outer + inner IP/프로토콜/포트 모두 반영
sysctl -w net.ipv4.fib_multipath_hash_policy=3
sysctl -w net.ipv4.fib_multipath_hash_fields=$(( \
    0x0001 | 0x0002 | 0x0004 | \
    0x0040 | 0x0080 | 0x0100 | 0x0400 | 0x0800 ))

# 핵심 비트:
# 0x0040 inner source IP, 0x0080 inner destination IP
# 0x0100 inner protocol, 0x0400 inner source port, 0x0800 inner destination port
선택 기준: underlay 장비를 제어할 수 없고 ECMP 분산이 중요하면 GRE-in-UDP가 가장 현실적입니다. 반대로 underlay가 단일 경로이거나 멀티캐스트 전달이 더 중요하면 순수 GRE가 더 단순합니다.

GRE vs VXLAN 성능 비교

항목GREVXLAN
캡슐화 오버헤드 24~36바이트 50바이트
ECMP 해싱 불리 (IP proto 47, 포트 없음) 유리 (UDP 소스 포트 엔트로피)
NAT 통과 불가 가능 (UDP)
CPU 사용 낮음 (단순 헤더) 중간 (UDP 처리 추가)
HW 오프로드 광범위 지원 광범위 지원
멀티캐스트 네이티브 지원 외부 학습 필요 (FDB)

디버깅

디버깅 도구

# 1. 터널 상태 확인
ip tunnel show
ip -d link show gre1
ip -s link show gre1     # RX/TX 바이트, 패킷, 에러 통계
ip monitor link          # 링크 상태 변화 실시간 관찰

# 2. tcpdump로 GRE 패킷 캡처
tcpdump -i eth0 proto gre -nn -v
# → 외부 IP + GRE 헤더 + 내부 패킷 확인

# 내부 패킷도 디코딩
tcpdump -i eth0 proto gre -nn -v -e

# GRE 터널 인터페이스에서 캡처 (디캡슐레이션된 패킷)
tcpdump -i gre1 -nn -v

# 3. ip route get으로 경로 확인
ip route get 192.168.100.2 dev gre1

# 4. 커널 로그 확인
dmesg | grep -i gre
journalctl -k | grep -i tunnel

# 5. conntrack 확인 (GRE 세션)
conntrack -L -p gre

# 5-1. 드롭 원인 추적
perf trace -e skb:kfree_skb -a sleep 5
dropwatch -l kas

# 6. PMTUD 확인 (ICMP 에러 모니터)
tcpdump -i eth0 'icmp[0] == 3 and icmp[1] == 4' -nn -v
# → ICMP Destination Unreachable, Fragmentation Needed

# 7. 터널/라우팅 통계
nstat -az | grep -E 'InNoRoutes|InHdrErrors|OutFragFails|InCsumErrors'

자주 발생하는 문제

증상원인해결 방법
터널 인터페이스 UP이나 ping 불가 방화벽에서 프로토콜 47 차단 iptables -A INPUT -p gre -j ACCEPT
소형 패킷은 통과, 대형 패킷 실패 MTU 불일치 / PMTUD 블랙홀 MTU 수동 설정 + MSS clamping
RX 패킷 카운터 증가하나 전달 안 됨 rp_filter (역방향 경로 필터) sysctl net.ipv4.conf.gre1.rp_filter=0
터널 생성 시 "RTNETLINK: File exists" gre0 기본 디바이스 이름 충돌 ip link 스타일 사용 또는 다른 이름 지정
GRE over NAT 실패 GRE는 포트 없어 NAT 불가 GRE-in-UDP (FOU) 사용
TTL 초과 루프 재귀 라우팅 (터널 경로가 터널 자체) TTL 고정, 라우팅 테이블(Routing Table) 확인
conntrack 테이블 가득 참 GRE 세션 누적 nf_conntrack_max 증가 또는 GRE notrack
ECMP 환경에서 seq 드롭 시퀀스 번호 순서 어긋남 시퀀스 번호 비활성화

ftrace를 이용한 커널 내부 추적

# GRE 관련 커널 함수 추적
cd /sys/kernel/debug/tracing

# function_graph 트레이서 활성화
echo function_graph > current_tracer
echo 'ip_tunnel_xmit' >> set_graph_function
echo 'ip_tunnel_rcv' >> set_graph_function
echo 'gre_rcv' >> set_graph_function
echo 'gre_build_header' >> set_graph_function
echo 1 > tracing_on

# 터널을 통해 패킷 전송 후 결과 확인
cat trace

# kprobe로 GRE 터널 lookup 추적
echo 'p:gre_lookup ip_tunnel_lookup remote=%si local=%dx key=%cx' \
    > kprobe_events
echo 1 > events/kprobes/gre_lookup/enable
cat trace_pipe

sysctl 파라미터

파라미터기본값설명
net.ipv4.conf.gre1.rp_filter 0 (커널 기본, 배포판 오버라이드 가능) 역방향 경로 필터링. 비대칭 라우팅이면 0 또는 2(loose) 검토
net.ipv4.conf.gre1.forwarding 0 터널을 통한 IP 포워딩 활성화 필요
net.ipv4.conf.gre1.accept_local 0 로컬 소스 주소 패킷 수신 허용. same-host 터널 종단이나 특수 라우팅에서 사용
net.ipv4.conf.gre1.disable_policy 0 IPSec 정책 무시 (IPSec 미사용 시)
net.ipv4.ip_forward 0 전역 IP 포워딩 — GRE 라우터 역할 시 1 필수
net.ipv4.conf.all.send_redirects 1 ICMP 리디렉트 전송. GRE 라우터에서는 0 권장
net.ipv4.ip_no_pmtu_disc 0 전역 PMTUD 비활성화 (비권장 — 터널별 설정 사용)
net.ipv4.fib_multipath_hash_policy 0 Linux host의 multipath 해시 기준. 2는 inner L3, 3은 사용자 정의 필드 사용
net.ipv4.fib_multipath_hash_fields 커널/배포판별 policy=3일 때 outer/inner IP·프로토콜·포트 비트마스크를 선택
# GRE 라우터 기본 sysctl 설정
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv4.conf.gre1.rp_filter=0
sysctl -w net.ipv4.conf.gre1.forwarding=1
sysctl -w net.ipv4.conf.all.send_redirects=0
sysctl -w net.ipv4.fib_multipath_hash_policy=2

# 영구 적용 (/etc/sysctl.d/99-gre.conf)
net.ipv4.ip_forward = 1
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.fib_multipath_hash_policy = 2

커널 소스 맵

파일역할
include/net/gre.hGRE 헤더 구조체(Struct), 플래그 매크로, gre_protocol 등록 API
include/net/ip_tunnels.hstruct ip_tunnel, ip_tunnel_parm, 터널 API 선언
include/net/erspan.hERSPAN 헤더 구조체 (Type II/III)
include/uapi/linux/if_tunnel.hUAPI: 터널 ioctl/netlink 상수, ip_tunnel_parm
net/ipv4/gre_demux.cGRE 프로토콜 47 디먹싱, 버전별 핸들러 디스패치(Dispatch)
net/ipv4/gre_offload.cGRE GSO/GRO 오프로드 콜백(Callback)
net/ipv4/ip_gre.cIPv4 GRE/GRETAP/ERSPAN 터널 디바이스 드라이버
net/ipv4/ip_tunnel.cIPv4 터널 공통 로직 (lookup, xmit, rcv, change, delete)
net/ipv4/ip_tunnel_core.ciptunnel_xmit/rcv, tunnel metadata, FOU 캡슐화
net/ipv6/ip6_gre.cIPv6 GRE/GRETAP/ERSPAN 터널 디바이스 드라이버
drivers/net/ppp/pptp.cPPTP (Enhanced GRE v1) 드라이버
net/netfilter/nf_conntrack_proto_gre.cGRE conntrack 프로토콜 핸들러
net/ipv4/netfilter/nf_nat_pptp.cPPTP NAT helper (GRE Call ID NAT)

GRE 프로토콜 상세

GRE(Generic Routing Encapsulation)는 RFC 2784(기본 GRE)와 RFC 2890(Key/Sequence 확장)에 의해 정의됩니다. 원래 RFC 1701에서 정의된 것이 RFC 2784에서 단순화되었습니다.

GRE 헤더 구조 상세

GRE 헤더 (RFC 2784 + RFC 2890) 0 1 2 3 12 15 16 31 C R K S Reserved0 (9비트, 0) Ver Protocol Type (16비트) C=1: 체크섬 존재 K=1: 키 존재 Checksum (16비트, C=1일 때) Reserved1 (16비트, 0) 선택적 (4바이트) Key (32비트, K=1일 때) 선택적 (4바이트) Sequence Number (32비트, S=1일 때) 선택적 (4바이트) GRE 헤더 크기 (플래그에 따라 가변) 기본 (C=K=S=0): 4바이트 Key만 (K=1): 8바이트 Checksum+Key (C=K=1): 12바이트 전부 (C=K=S=1): 16바이트 외부 IP 헤더(20) + GRE(4~16) = 총 오버헤드 24~36바이트

주요 Protocol Type 값

Protocol Type설명Linux 터널 타입
0x0800IPv4gre
0x86DDIPv6gre (IPv6 페이로드)
0x6558Transparent Ethernet Bridging (TEB)gretap
0x22EBERSPAN Type IIerspan
0x88BEERSPAN Type IIIerspan
0x880BPPP (PPTP Enhanced GRE)pptp

GRE over IPv6 및 FOU/GUE

ip6gre 터널

IPv6 underlay 위에 GRE 터널을 구성하려면 ip6gre 또는 ip6gretap 타입을 사용합니다. 커널 모듈은 ip6_gre.ko입니다.

# IPv6 GRE 터널 (L3)
ip link add gre6-1 type ip6gre \
    local 2001:db8::1 remote 2001:db8::2 \
    key 100

ip addr add 10.10.0.1/30 dev gre6-1
ip link set gre6-1 up

# IPv6 GRETAP 터널 (L2)
ip link add gretap6-1 type ip6gretap \
    local 2001:db8::1 remote 2001:db8::2

ip link set gretap6-1 up
ip link set gretap6-1 master br0

# IPv6 ERSPAN
ip link add erspan6 type ip6erspan \
    local 2001:db8::1 remote 2001:db8::2 \
    erspan_ver 2 erspan_dir egress \
    erspan_hwid 0x7

FOU와 GUE 캡슐화

FOU (Foo-over-UDP)GUE (Generic UDP Encapsulation)는 GRE를 UDP 안에 추가로 캡슐화하여 NAT 통과성과 ECMP 해시 분산을 개선합니다. RFC 8086에서 GRE-in-UDP를 정의합니다.

# FOU 수신 포트 설정 (커널에서 UDP 디캡슐화)
modprobe fou
ip fou add port 5555 ipproto 47  # 47 = GRE

# FOU 캡슐화된 GRE 터널
ip link add gre-fou type gre \
    local 10.0.0.1 remote 10.0.0.2 \
    encap fou encap-sport auto encap-dport 5555

ip addr add 172.16.0.1/30 dev gre-fou
ip link set gre-fou up

# GUE: 확장 헤더 지원 (FOU보다 유연)
ip fou add port 6666 gue
ip link add gre-gue type gre \
    local 10.0.0.1 remote 10.0.0.2 \
    encap gue encap-sport auto encap-dport 6666

# FOU/GUE의 장점:
# 1. UDP 소스 포트 엔트로피 → ECMP 해시 분산
# 2. NAT 통과 (UDP 포트 매핑)
# 3. RSS 활용 (NIC의 UDP 4-tuple 해시)
ECMP 해시 분산: 일반 GRE는 외부 IP 헤더만 있어서 ECMP 라우터가 모든 터널 트래픽을 단일 경로로 보냅니다. FOU/GUE는 UDP 소스 포트에 내부 패킷의 해시를 넣어 다중 경로에 자동 분산됩니다.

ERSPAN 상세

ERSPAN (Encapsulated Remote SPAN)은 GRE 터널을 통해 원격 패킷 미러링(Mirroring)을 수행하는 프로토콜입니다. Cisco가 개발했으며, Linux 커널 4.14부터 지원합니다.

ERSPAN 타입 비교

구분Type IType II (RFC 1701 기반)Type III
GRE Seq없음있음있음
ERSPAN 헤더없음 (원시 미러링)8바이트 (ver=1)12바이트 + 8바이트 (ver=2)
Session IDGRE Key의 하위 10비트헤더 필드 10비트헤더 필드 10비트
타임스탬프없음없음32비트 (100μs 단위)
방향없음없음ingress/egress 표시
HW ID없음없음소스 포트/인터페이스 식별
커널 지원4.14+4.14+4.16+
# ERSPAN Type II (ver 1) 설정
ip link add erspan1 type erspan \
    local 10.0.0.1 remote 10.0.0.2 \
    erspan_ver 1 erspan 100  # session ID = 100

# ERSPAN Type III (ver 2) 설정 — 타임스탬프와 방향 포함
ip link add erspan2 type erspan \
    local 10.0.0.1 remote 10.0.0.2 \
    erspan_ver 2 erspan_dir ingress \
    erspan_hwid 0x3

# TC를 이용한 미러링 설정
tc qdisc add dev eth0 ingress
tc filter add dev eth0 ingress \
    matchall \
    action mirred egress mirror dev erspan1

# 수신 측: ERSPAN 디캡슐화 후 패킷 캡처
ip link add erspan-rcv type erspan \
    local 10.0.0.2 remote 10.0.0.1 \
    erspan_ver 1 erspan 100
ip link set erspan-rcv up
tcpdump -i erspan-rcv -w mirror.pcap

GRE 터널 커널 구현 상세

커널의 GRE 터널 구현은 net/ipv4/ip_gre.c (IPv4)와 net/ipv6/ip6_gre.c (IPv6)에 있습니다. 공통 터널 로직은 net/ipv4/ip_tunnel.c에 구현되어 있습니다.

net_device_ops 구조

/* net/ipv4/ip_gre.c — GRE net_device_ops */
static const struct net_device_ops gre_netdev_ops = {
    .ndo_init          = gre_tunnel_init,
    .ndo_uninit        = ip_tunnel_uninit,
    .ndo_start_xmit    = gre_xmit,
    .ndo_siocdevprivate = ip_tunnel_siocdevprivate,
    .ndo_change_mtu    = ip_tunnel_change_mtu,
    .ndo_get_stats64   = dev_get_tstats64,
    .ndo_get_iflink    = ip_tunnel_get_iflink,
    .ndo_fill_metadata_dst = gre_fill_metadata_dst,
};

/* GRETAP용 net_device_ops (L2 브리징 지원) */
static const struct net_device_ops gre_tap_netdev_ops = {
    .ndo_init          = gre_tap_init,
    .ndo_uninit        = ip_tunnel_uninit,
    .ndo_start_xmit    = gre_tap_xmit,
    .ndo_set_mac_address = eth_mac_addr,
    .ndo_validate_addr = eth_validate_addr,
    .ndo_change_mtu    = ip_tunnel_change_mtu,
    .ndo_get_stats64   = dev_get_tstats64,
    .ndo_get_iflink    = ip_tunnel_get_iflink,
    .ndo_fill_metadata_dst = gre_fill_metadata_dst,
};

송신 경로 (xmit)

/* ip_gre.c — GRE 송신 핵심 로직 (간략화) */
static netdev_tx_t gre_xmit(struct sk_buff *skb,
                             struct net_device *dev)
{
    struct ip_tunnel *tunnel = netdev_priv(dev);
    const struct iphdr *tnl_params = &tunnel->parms.iph;

    /* 1. collect_md 모드이면 skb 메타데이터에서 터널 파라미터 추출 */
    if (tunnel->collect_md) {
        gre_fb_xmit(skb, dev, skb->protocol);
        return NETDEV_TX_OK;
    }

    /* 2. 내부 패킷의 프로토콜 확인 */
    if (skb->protocol == htons(ETH_P_IP))
        /* IPv4: PMTU 검사, DF 비트 처리 */
        ...

    /* 3. GRE 헤더 구성 */
    __be16 flags = tunnel->parms.o_flags;
    __be16 proto = tunnel->parms.iph.protocol;

    /* 4. ip_tunnel_xmit()으로 외부 IP 헤더 추가 + 전송 */
    __gre_xmit(skb, dev, tnl_params, proto);
    return NETDEV_TX_OK;
}

/* __gre_xmit → gre_build_header → ip_tunnel_xmit */
static void __gre_xmit(struct sk_buff *skb,
    struct net_device *dev,
    const struct iphdr *tnl_params, __be16 proto)
{
    struct ip_tunnel *t = netdev_priv(dev);

    /* GRE 헤더 빌드: flags, proto, key, seq를 skb에 push */
    gre_build_header(skb, t->tun_hlen,
                     t->parms.o_flags,
                     proto, t->parms.o_key,
                     htonl(t->o_seqno));

    /* 외부 IP 헤더 추가 + 라우팅 + 전송 */
    ip_tunnel_xmit(skb, dev, tnl_params, tnl_params->protocol);
}

수신 경로 (rcv)

/* gre_demux.c → ip_gre.c 수신 흐름 */

/* 1단계: gre_demux.c에서 GRE 프로토콜 47 수신 */
/* → GRE 버전 확인 → v0이면 gre_rcv() 호출 */

/* 2단계: ip_gre.c의 gre_rcv() */
static int gre_rcv(struct sk_buff *skb)
{
    struct tnl_ptk_info tpi;

    /* GRE 헤더 파싱: flags, proto, key, seq, csum 추출 */
    if (gre_parse_header(skb, &tpi, &csum_err,
                         htons(ETH_P_IP), 0) < 0)
        goto drop;

    /* 터널 룩업: (remote, local, key) */
    tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex,
                              tpi.flags, iph->saddr,
                              iph->daddr, tpi.key);
    if (!tunnel)
        goto drop;

    /* Protocol Type에 따라 처리 */
    if (tpi.proto == htons(ETH_P_TEB))
        /* GRETAP: Ethernet 프레임으로 처리 */
        ip_tunnel_rcv(tunnel, skb, &tpi, tun_dst, log_ecn_error);
    else if (tpi.proto == htons(ETH_P_ERSPAN) ||
             tpi.proto == htons(ETH_P_ERSPAN2))
        /* ERSPAN: 미러링 헤더 파싱 후 처리 */
        erspan_rcv(skb, &tpi, htons(ETH_P_TEB));
    else
        /* 일반 GRE: IP 패킷으로 처리 */
        ip_tunnel_rcv(tunnel, skb, &tpi, tun_dst, log_ecn_error);

    return 0;
}

PMTU 처리 상세

GRE 터널에서 PMTU (Path MTU Discovery)는 가장 빈번한 장애 원인입니다. 캡슐화로 인한 오버헤드와 중간 경로의 MTU 제약이 결합되어 문제가 복잡해집니다.

GRE 터널 PMTU Discovery 흐름 송신 호스트 MTU=1500 터널 입구 GRE encap 중간 라우터 MTU=1400! 터널 출구 GRE decap 수신 호스트 원본: 1500B + IP(20) + GRE(8) = 1528B ICMP Frag Needed (MTU=1400) 터널 입구 PMTU 처리 tunnel MTU = 1400 - 20(IP) - 8(GRE) = 1372 바이트 DF 비트 동작 내부 DF=1 → 외부 DF=1 (기본) 내부 DF=0 → 외부 DF=0 (단편화 허용) 실무 해결 전략 1) MSS clamping (iptables -j TCPMSS --clamp-mss-to-pmtu) 2) nopmtudisc 설정 3) underlay MTU 확보
# PMTU 관련 설정

# 1. 터널 MTU 직접 설정
ip link set gre1 mtu 1372

# 2. PMTUD 비활성화 (DF=0 강제, 단편화 허용)
ip tunnel change gre1 pmtudisc off
# 또는 생성 시: ip tunnel add gre1 ... nopmtudisc

# 3. MSS Clamping — TCP SYN의 MSS 옵션 조정
iptables -t mangle -A FORWARD -o gre1 \
    -p tcp --tcp-flags SYN,RST SYN \
    -j TCPMSS --clamp-mss-to-pmtu

# 4. DF 비트 제어
ip tunnel change gre1 pmtudisc on   # DF=1 (PMTUD 활성)
ip tunnel change gre1 pmtudisc off  # DF=0 (단편화 허용)

# 5. PMTU 캐시 확인
ip route get 10.0.0.2
# → ... mtu 1400 ...

# 6. ICMP Frag Needed 차단 여부 확인
tcpdump -i eth0 'icmp[icmptype] == 3 and icmp[icmpcode] == 4'
ICMP 블랙홀(Black Hole): 중간 방화벽이 ICMP를 차단하면 PMTUD가 동작하지 않아 패킷이 무한히 드롭됩니다. 이를 PMTU 블랙홀이라 합니다. 증상: 작은 패킷(ping)은 통과하지만 큰 패킷(SSH, HTTP)은 멈춤. 해결: MSS clamping 적용 또는 underlay에서 ICMP type 3 code 4를 반드시 허용합니다.

라우팅과 정책

터널 라우팅

# GRE 터널 위로 정적 라우트 설정
ip route add 192.168.100.0/24 dev gre1

# 특정 게이트웨이를 통한 라우팅
ip route add 10.20.0.0/16 via 172.16.0.2 dev gre1

# 정책 라우팅: 특정 소스에서 온 트래픽만 터널로
ip rule add from 192.168.1.0/24 table 100
ip route add default dev gre1 table 100

# VRF 연동: 터널을 VRF에 귀속
ip link add vrf-tunnel type vrf table 200
ip link set vrf-tunnel up
ip link set gre1 master vrf-tunnel

# VRF 내에서 라우트 추가
ip route add 10.30.0.0/16 dev gre1 table 200

# 동적 라우팅 프로토콜 (FRR/BIRD)과 연동
# BGP/OSPF가 GRE 터널을 인터페이스로 인식
# → next-hop으로 터널 피어 주소 사용

ECMP 해시와 GRE

sysctl 파라미터기본값GRE 영향
net.ipv4.fib_multipath_hash_policy00=L3 해시(src/dst IP만), 1=L4 해시(포트 포함), 2=L3+내부 헤더
net.ipv4.fib_multipath_hash_fields(커널별)해시에 포함할 필드 비트마스크 (5.12+)
net.ipv6.fib_multipath_hash_policy0IPv6 ECMP 해시 정책
# ECMP 해시 정책 설정
# 정책 2: 내부(inner) 헤더도 해시에 포함 → GRE 트래픽 분산 개선
sysctl -w net.ipv4.fib_multipath_hash_policy=2

# 또는 FOU/GUE 사용 → UDP 포트 엔트로피로 자연스럽게 분산

성능 최적화

GSO/GRO 오프로드 상세

GRE 터널의 성능은 GSO (Generic Segmentation Offload)GRO (Generic Receive Offload) 지원에 크게 의존합니다.

오프로드 타입SKB 플래그설명확인 방법
GRE GSOSKB_GSO_GREGRE 캡슐화된 TSO 세그먼트ethtool -k eth0 | grep gre
GRE GSO + CsumSKB_GSO_GRE_CSUMGRE 체크섬 포함 GSOtx-gre-csum-segmentation
GRE GRO-수신 측 GRE 패킷 병합ethtool -K eth0 rx-gre-hw-offload on
FOU/GUE GSOSKB_GSO_UDP_TUNNELUDP 캡슐화 GSOtx-udp_tnl-segmentation
# NIC의 GRE 오프로드 지원 확인
ethtool -k eth0 | grep -E "gre|udp_tnl|encap"
# tx-gre-segmentation: on
# tx-gre-csum-segmentation: on
# rx-gre-hw-offload: on
# tx-udp_tnl-segmentation: on

# 오프로드 활성화/비활성화
ethtool -K eth0 tx-gre-segmentation on
ethtool -K eth0 rx-gre-hw-offload on

# GRO 성능 확인 (병합된 패킷 수)
ethtool -S eth0 | grep gro
# rx_lro_pkts: 12345  (GRO로 병합된 패킷 수)

# 체크섬 오프로드 상태
ethtool -k eth0 | grep checksum
# tx-checksum-ip-generic: on
# rx-checksum: on

하드웨어 가속

NIC 벤더지원 기능드라이버
Intel (i40e/ice)GRE TX/RX offload, GSO/GRO, 체크섬, RSSi40e, ice
Mellanox (mlx5)GRE 전체 offload, flow steering, 체크섬mlx5_core
Broadcom (bnxt)GRE GSO, 체크섬 offloadbnxt_en
Netronome (nfp)GRE encap/decap offload, TC flowernfp

GRE over IPsec

GRE는 자체 암호화(Encryption)가 없으므로, 보안이 필요한 환경에서는 IPsec과 결합합니다. 두 가지 구성 방식이 있습니다.

IPsec 결합 방식

방식패킷 구조장점단점
Transport Mode IP → ESP → GRE → Payload 오버헤드가 적음 터널 엔드포인트 = IPsec 엔드포인트여야 함
Tunnel Mode IP → ESP → IP → GRE → Payload 엔드포인트 분리 가능 이중 IP 헤더로 오버헤드 증가 (40+ 바이트)
# GRE over IPsec (Transport Mode) 설정

# 1. GRE 터널 생성
ip tunnel add gre-ipsec mode gre \
    local 10.0.0.1 remote 10.0.0.2 \
    key 42
ip addr add 172.16.0.1/30 dev gre-ipsec
ip link set gre-ipsec up

# 2. IPsec SA 설정 (strongSwan/Libreswan 또는 수동)
ip xfrm state add src 10.0.0.1 dst 10.0.0.2 \
    proto esp spi 0x1001 mode transport \
    enc "aes" 0x$(head -c 16 /dev/urandom | xxd -p) \
    auth "hmac(sha256)" 0x$(head -c 32 /dev/urandom | xxd -p)

# 3. IPsec 정책: GRE 프로토콜(47)을 IPsec으로 보호
ip xfrm policy add src 10.0.0.1 dst 10.0.0.2 \
    proto gre dir out \
    tmpl src 10.0.0.1 dst 10.0.0.2 \
    proto esp mode transport

ip xfrm policy add src 10.0.0.2 dst 10.0.0.1 \
    proto gre dir in \
    tmpl src 10.0.0.2 dst 10.0.0.1 \
    proto esp mode transport

# 4. 방화벽: ESP(프로토콜 50)와 IKE(UDP 500/4500) 허용
iptables -A INPUT -p esp -j ACCEPT
iptables -A INPUT -p udp --dport 500 -j ACCEPT
iptables -A INPUT -p udp --dport 4500 -j ACCEPT

터널 필터링

# GRE 터널 인터페이스에 대한 방화벽 규칙

# GRE 프로토콜(47) 자체 필터링
iptables -A INPUT -p gre -s 10.0.0.2 -j ACCEPT
iptables -A INPUT -p gre -j DROP

# GRE 터널 내부 트래픽 필터링
iptables -A FORWARD -i gre1 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o gre1 \
    -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i eth0 -o gre1 -j DROP

# nftables로 GRE 필터링
nft add rule inet filter input \
    ip protocol gre ip saddr 10.0.0.2 accept
nft add rule inet filter input \
    ip protocol gre drop

실전 예제 종합

사이트 간 GRE 터널 스크립트

#!/bin/bash
# 사이트 간 GRE 터널 설정 스크립트
# Site A: 10.0.0.1 (LAN: 192.168.1.0/24)
# Site B: 10.0.0.2 (LAN: 192.168.2.0/24)

LOCAL_IP="10.0.0.1"
REMOTE_IP="10.0.0.2"
TUNNEL_IP="172.16.0.1/30"
REMOTE_LAN="192.168.2.0/24"
GRE_KEY=1234

# GRE 터널 생성
ip link add site-b type gre \
    local $LOCAL_IP remote $REMOTE_IP \
    key $GRE_KEY ttl 64

# MTU 설정 (underlay MTU - IP(20) - GRE(8) - Key(4))
ip link set site-b mtu 1468

# 터널 IP 할당 및 활성화
ip addr add $TUNNEL_IP dev site-b
ip link set site-b up

# 원격 LAN으로의 라우트
ip route add $REMOTE_LAN dev site-b

# MSS Clamping
iptables -t mangle -A FORWARD -o site-b \
    -p tcp --tcp-flags SYN,RST SYN \
    -j TCPMSS --clamp-mss-to-pmtu

# IP 포워딩 활성화
sysctl -w net.ipv4.ip_forward=1

# rp_filter 완화 (터널 인터페이스)
sysctl -w net.ipv4.conf.site-b.rp_filter=2

echo "GRE tunnel to $REMOTE_IP established"

Overlay 네트워크 구성

# GRETAP 기반 L2 오버레이 네트워크
# 3개 노드: Node1(10.0.0.1), Node2(10.0.0.2), Node3(10.0.0.3)

# Node1에서 실행:
# 브리지 생성
ip link add br-overlay type bridge
ip link set br-overlay up
ip addr add 10.100.0.1/24 dev br-overlay

# Node2로의 GRETAP
ip link add gretap-n2 type gretap \
    local 10.0.0.1 remote 10.0.0.2 key 100
ip link set gretap-n2 master br-overlay
ip link set gretap-n2 up

# Node3로의 GRETAP
ip link add gretap-n3 type gretap \
    local 10.0.0.1 remote 10.0.0.3 key 100
ip link set gretap-n3 master br-overlay
ip link set gretap-n3 up

# 로컬 컨테이너/VM 연결
ip link add veth-c1 type veth peer name veth-c1-br
ip link set veth-c1-br master br-overlay
ip link set veth-c1-br up
# veth-c1을 컨테이너/네임스페이스에 할당

DMVPN 스타일 구성

# DMVPN (Dynamic Multipoint VPN) 스타일 구성
# Hub: 10.0.0.1, Spoke1: 10.0.0.2, Spoke2: 10.0.0.3

# --- Hub 설정 ---
ip tunnel add mgre0 mode gre \
    local 10.0.0.1 key 999 ttl 64
ip addr add 172.16.0.1/24 dev mgre0
ip link set mgre0 up

# Hub에서 NHRP 캐시 수동 추가 (또는 OpenNHRP/FRR 사용)
ip neigh add 172.16.0.2 lladdr 10.0.0.2 dev mgre0 nud permanent
ip neigh add 172.16.0.3 lladdr 10.0.0.3 dev mgre0 nud permanent

# Hub의 각 Spoke LAN 라우트
ip route add 192.168.2.0/24 via 172.16.0.2 dev mgre0
ip route add 192.168.3.0/24 via 172.16.0.3 dev mgre0

# --- Spoke1 설정 ---
ip tunnel add mgre0 mode gre \
    local 10.0.0.2 key 999 ttl 64
ip addr add 172.16.0.2/24 dev mgre0
ip link set mgre0 up

# Hub를 default next-hop으로
ip neigh add 172.16.0.1 lladdr 10.0.0.1 dev mgre0 nud permanent
ip route add 192.168.1.0/24 via 172.16.0.1 dev mgre0
ip route add 192.168.3.0/24 via 172.16.0.1 dev mgre0
NHRP와 Spoke-to-Spoke: 순수 GRE mGRE만으로는 Spoke 간 직접 통신이 불가능합니다. NHRP(Next Hop Resolution Protocol)를 사용하면 Hub를 통해 Spoke의 NBMA(Non-Broadcast Multi-Access) 주소를 조회하고, Spoke-to-Spoke 직접 터널을 동적으로 생성할 수 있습니다. Linux에서는 FRR(Free Range Routing)의 nhrpd 또는 OpenNHRP를 사용합니다.

GRE 장애 진단 체크리스트

증상점검 항목명령어
터널 인터페이스 UP이지만 ping 불가 외부 IP 간 연결성, 방화벽 GRE(47) 허용 ping 원격_IP, iptables -L -v -n | grep gre
작은 패킷만 통과 MTU/PMTU 문제, ICMP 차단 ping -s 1400 -M do 원격, tcpdump icmp
SSH 접속 후 멈춤 PMTU 블랙홀 (MSS clamping 필요) iptables -t mangle -L FORWARD
간헐적 패킷 드롭 시퀀스 번호 검증 + ECMP 재정렬 ip -s link show gre1 (rx_fifo_errors 확인)
Key 불일치 양쪽 ikey/okey 교차 확인 ip -d link show gre1
NAT 환경 터널 불가 GRE는 포트 없어 NAT 어려움 FOU/GUE로 UDP 캡슐화 또는 IPsec NAT-T 사용
rp_filter 드롭 역방향 경로 검증 실패 sysctl net.ipv4.conf.gre1.rp_filter → 0 또는 2
conntrack 테이블 가득 참 GRE conntrack 항목 과다 conntrack -L -p gre | wc -l

터널 모니터링 스크립트

#!/bin/bash
# GRE 터널 상태 모니터링 스크립트

for dev in $(ip -o link show type gre | awk -F: '{print $2}' | tr -d ' '); do
    echo "=== $dev ==="

    # 터널 파라미터
    ip -d link show $dev | grep -E "gre|local|remote|key|mtu"

    # 트래픽 통계
    ip -s link show $dev | tail -4

    # 링크 상태
    carrier=$(cat /sys/class/net/$dev/carrier 2>/dev/null)
    echo "  carrier: ${carrier:-unknown}"

    # PMTU 확인
    remote=$(ip -d link show $dev | grep -oP 'remote \K[0-9.]+')
    if [ -n "$remote" ]; then
        pmtu=$(ip route get $remote 2>/dev/null | grep -oP 'mtu \K[0-9]+')
        echo "  PMTU to $remote: ${pmtu:-unknown}"
    fi

    echo ""
done

BPF/TC를 이용한 GRE 제어

eBPF와 TC(Traffic Control)를 활용하면 GRE 터널의 동작을 프로그래밍 가능한 방식으로 제어할 수 있습니다. collect_md(external) 모드 터널과 결합하면 패킷별로 터널 파라미터를 동적으로 설정할 수 있습니다.

TC flower를 이용한 GRE 분류

# external 모드 GRE 디바이스 생성
ip link add gre-bpf type gre external
ip link set gre-bpf up

# TC flower로 GRE 키 기반 분류
tc qdisc add dev gre-bpf ingress

# Key=100인 GRE 패킷을 veth0으로 리다이렉트
tc filter add dev gre-bpf ingress \
    flower enc_key_id 100 \
    action tunnel_key unset \
    action mirred egress redirect dev veth0

# Key=200인 GRE 패킷을 veth1으로 리다이렉트
tc filter add dev gre-bpf ingress \
    flower enc_key_id 200 \
    action tunnel_key unset \
    action mirred egress redirect dev veth1

# 송신 방향: 목적지 서브넷별로 다른 GRE 키 할당
tc qdisc add dev eth0 ingress

tc filter add dev eth0 ingress \
    flower dst_ip 192.168.100.0/24 \
    action tunnel_key set id 100 \
    dst_ip 10.0.0.2 \
    action mirred egress redirect dev gre-bpf

tc filter add dev eth0 ingress \
    flower dst_ip 192.168.200.0/24 \
    action tunnel_key set id 200 \
    dst_ip 10.0.0.3 \
    action mirred egress redirect dev gre-bpf

BPF tunnel_key 헬퍼

/* BPF TC 프로그램에서 GRE 터널 키 설정 */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("tc")
int gre_encap(struct __sk_buff *skb)
{
    struct bpf_tunnel_key key = {};

    /* 목적지 IP에 따라 GRE 키와 원격 주소 설정 */
    key.tunnel_id = 42;            /* GRE Key */
    key.remote_ipv4 = 0x0200000A;  /* 10.0.0.2 */
    key.tunnel_ttl = 64;

    bpf_skb_set_tunnel_key(skb, &key,
        sizeof(key), 0);

    return TC_ACT_OK;
}

SEC("tc")
int gre_decap(struct __sk_buff *skb)
{
    struct bpf_tunnel_key key = {};
    int ret;

    /* 수신된 GRE 패킷의 터널 키 추출 */
    ret = bpf_skb_get_tunnel_key(skb, &key,
        sizeof(key), 0);
    if (ret < 0)
        return TC_ACT_SHOT;

    /* tunnel_id(GRE Key)에 따라 처리 분기 */
    if (key.tunnel_id == 42)
        return bpf_redirect(veth_ifindex, 0);

    return TC_ACT_OK;
}

성능 튜닝 요약

튜닝 항목기본값권장값효과
GSO/GRO offload대부분 onon (NIC 지원 확인)CPU 사용률 30~50% 감소
tx-gre-segmentationNIC별 상이on큰 세그먼트를 NIC에서 분할
rp_filter1 (strict)0 또는 2 (loose)비대칭 경로 터널에서 드롭 방지
GRE 체크섬offoff (내부 L4 체크섬 존재 시)불필요한 CPU 오버헤드 제거
FOU/GUE 캡슐화미사용ECMP 환경에서 사용UDP 엔트로피로 다중 경로 분산
MTU자동 계산수동 설정 + MSS clampingPMTU 블랙홀 방지
txqueuelen1000워크로드별 조정버스트 트래픽 처리 개선

표준 및 공식 문서

아래 문서는 이 페이지(Page)를 검증하거나 실제 운영 환경에서 세부 동작을 확인할 때 가장 먼저 볼 1차 자료입니다.

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.