GRE (Generic Routing Encapsulation)

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

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

핵심 요약

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

단계별 이해

  1. 캡슐화 층 분리
    문제를 outer IP, GRE 옵션, inner payload 중 어디에서 보는지 먼저 고정합니다.
  2. 링크 종류 선택
    L3면 gre, L2면 gretap, 미러링이면 erspan, 동적 제어면 external을 고릅니다.
  3. underlay 제약 확인
    NAT, ECMP, 방화벽, 멀티패스 해시가 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)
오버헤드 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 프레임 브리지/OVS와 직접 연결되는 L2 오버레이 브로드캐스트 도메인이 그대로 확장되어 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) */
};
플래그 비트매크로설명추가 헤더 크기
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 필드 존재 (순서 보장) +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 핸들러 등록 프레임워크
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 헤더와 캡슐화된 페이로드 전체에 대한 무결성 검증입니다. 외부 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에서는 영향이 적지만, 소프트웨어 전용 환경에서는 처리량이 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 오프셋 매칭 주의: 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 세그먼트를 지연 분할하여 처리량을 극대화합니다:

/* 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별) 수신 패킷 병합으로 인터럽트/스택 처리 감소
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 규칙으로 터널 엔드포인트와 키를 동적 지정

네트워크 네임스페이스 간 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만으로 해시를 계산합니다. 그 결과 대역폭이 충분한 멀티패스 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 고정, 라우팅 테이블 확인
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 헤더 구조체, 플래그 매크로, 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 디먹싱, 버전별 핸들러 디스패치
net/ipv4/gre_offload.cGRE GSO/GRO 오프로드 콜백
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)

표준 및 공식 문서

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

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