L2TP (Layer 2 Tunneling Protocol)
L2TP는 Layer 2 프레임을 IP 네트워크 위에서 터널(Tunnel)링하는 프로토콜입니다.
리눅스 커널은 L2TPv2(PPP 터널링)와 L2TPv3(이더넷/HDLC 등 다양한 Layer 2 프로토콜 터널링)를
모두 지원하며, l2tp_core, l2tp_ppp, l2tp_eth,
l2tp_netlink 네 가지 핵심 모듈로 구성됩니다.
본 문서는 프로토콜 구조부터 커널 내부 아키텍처, 패킷(Packet) 포맷, 터널/세션 관리,
Netlink 설정 인터페이스, IPsec 연동, 실전 구성 예제, 디버깅(Debugging)까지 종합적으로 다룹니다.
핵심 요약
- L2TP 터널 --- 두 끝점(LAC/LNS) 사이의 제어/데이터 연결 컨테이너(Container)
- L2TP 세션 --- 터널 내부에서 실제 데이터를 주고받는 논리적 채널
- L2TPv2 --- PPP 프레임 전용 터널링 (RFC 2661)
- L2TPv3 --- 이더넷, HDLC 등 범용 Layer 2 터널링 (RFC 3931)
- Pseudowire --- L2TPv3에서 세션이 전달하는 가상 회선
단계별 이해
- 터널 생성
두 끝점 간 UDP 또는 IP 소켓(Socket)을 열고 L2TP 터널을 수립합니다. - 세션 생성
터널 안에 하나 이상의 세션을 만들어 실제 데이터 전달 경로를 구성합니다. - 캡슐화(Encapsulation) 및 전송
원본 Layer 2 프레임을 L2TP 헤더로 감싸서 IP 네트워크를 통해 전송합니다. - 역캡슐화(Decapsulation) 및 전달
수신 측에서 L2TP 헤더를 벗기고 원본 프레임을 로컬 인터페이스로 전달합니다.
L2TP 개요
L2TP란?
L2TP(Layer 2 Tunneling Protocol)는 데이터 링크 계층(Layer 2) 프레임을 IP 네트워크를 통해 터널링하는 프로토콜입니다. Microsoft의 PPTP(Point-to-Point Tunneling Protocol)와 Cisco의 L2F(Layer 2 Forwarding)를 결합하여 IETF에서 표준화했습니다.
VPN 터널링의 역사
| 연도 | 프로토콜 | RFC | 특징 |
|---|---|---|---|
| 1996 | PPTP | RFC 2637 | Microsoft, GRE 기반 PPP 터널링 |
| 1998 | L2F | RFC 2341 | Cisco, UDP 기반 PPP 터널링 |
| 1999 | L2TPv2 | RFC 2661 | PPTP + L2F 통합, PPP 전용 |
| 2005 | L2TPv3 | RFC 3931 | 범용 Layer 2 터널링, 이더넷/HDLC/FR 지원 |
L2TPv2와 L2TPv3 비교
| 속성 | L2TPv2 | L2TPv3 |
|---|---|---|
| 표준 | RFC 2661 | RFC 3931 |
| 전송 프로토콜 | UDP만 (포트 1701) | UDP 또는 IP (프로토콜 115) |
| 페이로드(Payload) 유형 | PPP 프레임만 | 이더넷, PPP, HDLC, FR 등 |
| 터널 ID 크기 | 16비트 | 32비트 |
| 세션 ID 크기 | 16비트 | 32비트 |
| 쿠키(인증) | 없음 | 최대 8바이트 |
| 주요 용도 | 원격 접속 VPN | L2 VPN, 사이트 간 연결, 이더넷 브리징 |
L2TP 프로토콜 구조
L2TP는 제어 메시지와 데이터 메시지의 두 가지 채널을 사용합니다. 제어 채널은 터널과 세션의 수립/해제를 담당하며 신뢰성 있는 전송(순서 번호, 재전송(Retransmission))을 보장합니다. 데이터 채널은 실제 페이로드를 전달하며 비신뢰성(best-effort)으로 동작합니다.
L2TP 프로토콜 스택
L2TPv2는 반드시 UDP(포트 1701) 위에서만 동작하지만, L2TPv3는 UDP 외에도 IP 프로토콜 번호 115를 사용하여 직접 IP 위에서 동작할 수 있습니다. IP 직접 캡슐화는 UDP 헤더 오버헤드(Overhead)를 제거하여 대역폭(Bandwidth) 효율이 높지만, NAT 환경에서는 사용이 어렵습니다.
L2TP 패킷 포맷
L2TPv2 제어 메시지 헤더
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|T|L|x|x|S|x|O|P|x|x|x|x| Ver | Length (opt) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Tunnel ID | Session ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Ns (opt) | Nr (opt) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Offset Size (opt) | Offset Padding (opt) ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
헤더 필드 설명
- T 비트 메시지 유형: 1=제어, 0=데이터
- L 비트 Length 필드 존재 여부 (제어 메시지에서 필수)
- S 비트 Ns/Nr 순서 번호 필드 존재 여부
- O 비트 Offset 필드 존재 여부 (데이터 메시지 전용)
- P 비트 우선순위(Priority) 비트 (데이터 메시지 전용)
- Ver 프로토콜 버전: L2TPv2=2
- Tunnel ID 16비트 터널 식별자 (수신 측이 할당)
- Session ID 16비트 세션 식별자 (수신 측이 할당)
- Ns/Nr 보내기/받기 순서 번호 (제어 메시지 신뢰성 보장용)
L2TPv3 데이터 메시지 헤더 (UDP 캡슐화)
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|T|L|x|x|S|x|O|P|x|x|x|x| Ver | Length (opt) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Session ID (32-bit) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cookie (optional, 0/4/8 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| L2-Specific Sublayer (opt) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
L2TPv3 데이터 메시지 헤더 (IP 직접 캡슐화)
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Session ID (32-bit) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cookie (optional, 0/4/8 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| L2-Specific Sublayer (opt) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
L2TPv3 over UDP 패킷 전체 구조
AVP (Attribute Value Pair)
L2TP 제어 메시지의 파라미터는 AVP 형태로 인코딩됩니다. 각 AVP는 다음 구조를 갖습니다.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|M|H| rsvd | Length | Vendor ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Attribute Type | Attribute Value ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
M 비트: 필수(Mandatory) AVP 표시. H 비트: 은닉(Hidden) AVP 표시. 주요 AVP 유형: Message Type(0), Protocol Version(2), Framing Capabilities(3), Bearer Capabilities(4), Tunnel ID(9), Session ID(14), Call Serial Number(15) 등.
L2TP 터널과 세션 개념
L2TP의 핵심 추상화는 터널(Tunnel)과 세션(Session)입니다. 하나의 터널 안에 여러 세션이 다중화(multiplexing)될 수 있으며, 각 세션은 독립적인 Layer 2 연결을 나타냅니다.
- LAC (L2TP Access Concentrator): 원격 사용자의 PPP 연결을 수신하여 L2TP 터널로 전달하는 장비
- LNS (L2TP Network Server): L2TP 터널을 종단하고 PPP 세션을 처리하는 서버
- L2TPv3에서는 LAC/LNS 대신 LCCE (L2TP Control Connection Endpoint)라는 용어를 사용
리눅스 커널 L2TP 아키텍처
리눅스 커널의 L2TP 서브시스템은 네 개의 핵심 모듈로 구성됩니다.
l2tp_core가 터널/세션 관리의 기반을 제공하고,
페이로드 유형에 따라 l2tp_ppp 또는 l2tp_eth가 세션 핸들러(Handler)로 동작합니다.
l2tp_netlink는 사용자 공간(User Space)과의 Netlink 기반 설정 인터페이스를 담당합니다.
커널 소스 파일 구조
net/l2tp/
l2tp_core.c /* 터널/세션 코어 로직, 캡슐화/역캡슐화 */
l2tp_core.h /* 핵심 자료구조 선언 */
l2tp_netlink.c /* Generic Netlink 인터페이스 */
l2tp_ppp.c /* PPPoL2TP 세션 핸들러 */
l2tp_eth.c /* L2TPv3 이더넷 세션 핸들러 */
l2tp_ip.c /* L2TPv3 over IPv4 소켓 */
l2tp_ip6.c /* L2TPv3 over IPv6 소켓 */
l2tp_debugfs.c /* debugfs 진단 인터페이스 */
L2TP 커널 모듈(Kernel Module) 구조
struct l2tp_tunnel
struct l2tp_tunnel은 L2TP 터널 하나를 표현하는 핵심 구조체(Struct)입니다.
커널 소스(net/l2tp/l2tp_core.h)에서 발췌한 주요 필드는 다음과 같습니다.
struct l2tp_tunnel {
int magic; /* 매직 넘버 검증 */
struct rcu_head rcu;
int fd; /* 소켓 파일 디스크립터 */
struct sock *sock; /* 터널 소켓 */
int version; /* 2=L2TPv2, 3=L2TPv3 */
u32 tunnel_id; /* 로컬 터널 ID */
u32 peer_tunnel_id; /* 원격 터널 ID */
enum l2tp_encap_type encap; /* UDP 또는 IP */
struct l2tp_tunnel_cfg cfg; /* 설정 파라미터 */
struct hlist_head session_hlist[L2TP_HASH_SIZE];
/* 세션 해시 테이블 */
struct list_head list; /* 전역 터널 리스트 */
struct net *l2tp_net; /* 네트워크 네임스페이스 */
refcount_t ref_count; /* 참조 카운트 */
struct l2tp_stats stats; /* 통계 카운터 */
struct dentry *l2tp_dentry; /* debugfs 엔트리 */
};
코드 설명
- magic 구조체 유효성 검증용 매직 넘버.
L2TP_TUNNEL_MAGIC(0x42114DDA)과 비교 - sock 터널에 바인딩된 커널 소켓. UDP 또는 IP 소켓이 올 수 있음
- version L2TP 프로토콜 버전. 2(L2TPv2) 또는 3(L2TPv3)
- encap 캡슐화 유형:
L2TP_ENCAPTYPE_UDP또는L2TP_ENCAPTYPE_IP - session_hlist 터널 내 세션을 빠르게 조회하기 위한 해시 테이블(Hash Table)
- l2tp_net 네트워크 네임스페이스(Namespace) 참조. 컨테이너 환경 지원
struct l2tp_session
struct l2tp_session {
int magic; /* 매직 넘버 검증 */
struct l2tp_tunnel *tunnel; /* 소속 터널 */
u32 session_id; /* 로컬 세션 ID */
u32 peer_session_id;/* 원격 세션 ID */
u8 cookie[8]; /* 세션 쿠키 (L2TPv3) */
int cookie_len;
u8 peer_cookie[8];
int peer_cookie_len;
u16 nr; /* 예상 수신 순서 번호 */
u16 ns; /* 다음 송신 순서 번호 */
enum l2tp_pwtype pwtype; /* pseudowire 유형 */
struct l2tp_session_cfg cfg; /* 세션 설정 */
struct l2tp_stats stats; /* 통계 */
void (*recv_skb)(struct l2tp_session *,
struct sk_buff *, int);
void (*session_close)(struct l2tp_session *);
struct hlist_node hlist; /* 터널 내 해시 노드 */
struct hlist_node global_hlist; /* 전역 해시 노드 */
refcount_t ref_count;
};
L2TP over UDP 동작 흐름
L2TP over UDP는 가장 일반적인 캡슐화 방식입니다. UDP 포트 1701을 사용하며, NAT 환경에서도 동작할 수 있다는 장점이 있습니다.
UDP 소켓에 L2TP 핸들러 등록
/* l2tp_core.c: 터널 생성 시 UDP 소켓에 콜백 등록 */
static int l2tp_tunnel_sock_create(struct net *net,
u32 tunnel_id,
struct l2tp_tunnel_cfg *cfg,
struct socket **sockp)
{
struct socket *sock;
struct udp_tunnel_sock_cfg udp_cfg = { };
/* UDP 소켓 생성 또는 기존 소켓 사용 */
udp_cfg.encap_type = UDP_ENCAP_L2TPINUDP;
udp_cfg.encap_rcv = l2tp_udp_encap_recv;
udp_cfg.encap_destroy = l2tp_udp_encap_destroy;
setup_udp_tunnel_sock(net, sock, &udp_cfg);
return 0;
}
코드 설명
- encap_type
UDP_ENCAP_L2TPINUDP는 UDP 소켓이 L2TP 패킷을 수신하도록 설정하는 캡슐화 유형 - encap_rcv
l2tp_udp_encap_recv()는 UDP 패킷이 도착할 때 호출되는 L2TP 수신 핸들러 - setup_udp_tunnel_sock UDP 터널 소켓 설정 헬퍼 함수.
inet_sk(sk)->encap_type을 설정하여 UDP 계층이 L2TP 콜백(Callback)을 호출하도록 함
L2TP over IP 동작 흐름
L2TPv3는 UDP 외에도 IP 프로토콜 번호 115를 사용하여 직접 IP 위에서 동작할 수 있습니다. 이 방식은 UDP 헤더(8바이트)를 제거하여 오버헤드를 줄이지만, NAT 환경에서 동작하지 않습니다.
/* l2tp_ip.c: L2TP over IP 프로토콜 핸들러 등록 */
static const struct net_protocol l2tp_ip_protocol = {
.handler = l2tp_ip_recv,
.err_handler = l2tp_ip_err,
};
static int __init l2tp_ip_init(void)
{
int err;
err = inet_add_protocol(&l2tp_ip_protocol, IPPROTO_L2TP);
if (err)
return err;
err = inet_register_protosw(&l2tp_ip_protosw);
if (err) {
inet_del_protocol(&l2tp_ip_protocol, IPPROTO_L2TP);
return err;
}
return 0;
}
코드 설명
- net_protocol IP 프로토콜 핸들러 구조체.
handler가 패킷 수신 시 호출됨 - IPPROTO_L2TP IP 프로토콜 번호 115 (L2TP)
- inet_add_protocol 커널 IP 스택에 L2TP 프로토콜 핸들러를 등록
- inet_register_protosw L2TP 소켓 유형을 소켓 스위치에 등록하여
socket(AF_INET, SOCK_DGRAM, IPPROTO_L2TP)호출 가능
| 속성 | L2TP over UDP | L2TP over IP |
|---|---|---|
| 오버헤드 | UDP 8B + L2TP 헤더 | L2TP 헤더만 |
| NAT 통과 | 가능 | 불가 (프로토콜 115) |
| 방화벽(Firewall) 통과 | 용이 (UDP 포트 기반) | 어려움 (IP 프로토콜 기반) |
| 커널 모듈 | l2tp_core (내장) | l2tp_ip / l2tp_ip6 |
| 주요 용도 | 원격 접속, NAT 환경 | 데이터센터 간 전용선 |
L2TPv3 이더넷 터널 (l2tp_eth)
l2tp_eth 모듈은 L2TPv3 세션 위에 가상 이더넷 인터페이스(l2tpethN)를
생성합니다. 이 인터페이스를 통해 원격 사이트 간 투명한 Layer 2 연결이 가능합니다.
/* l2tp_eth.c: 이더넷 세션 생성 시 네트워크 디바이스 등록 */
static int l2tp_eth_create(struct net *net,
struct l2tp_tunnel *tunnel,
u32 session_id, u32 peer_session_id,
struct l2tp_session_cfg *cfg)
{
struct net_device *dev;
struct l2tp_session *session;
struct l2tp_eth *priv;
dev = alloc_netdev(sizeof(*priv), "l2tpeth%d",
NET_NAME_UNKNOWN, l2tp_eth_dev_setup);
if (!dev)
return -ENOMEM;
session = l2tp_session_create(sizeof(*spriv),
tunnel, session_id,
peer_session_id, cfg);
session->recv_skb = l2tp_eth_dev_recv;
register_netdevice(dev);
return 0;
}
코드 설명
- alloc_netdev
l2tpethN이름 패턴으로 가상 네트워크 디바이스 할당 - l2tp_session_create
l2tp_core의 세션 생성 함수 호출 - recv_skb 수신 콜백을
l2tp_eth_dev_recv()로 설정하여 수신된 이더넷 프레임을 가상 인터페이스로 전달
l2tpeth 인터페이스 특성
- 브리지(Bridge) 가능:
brctl addif br0 l2tpeth0으로 브리지에 추가 가능 - VLAN 지원: 802.1Q VLAN 태그가 투명하게 전달됨
- MTU 조정: 터널 오버헤드를 고려하여 적절한 MTU 설정 필요
- MAC 주소: 랜덤 할당되며,
ip link set으로 변경 가능
PPP over L2TP (l2tp_ppp + pppd)
l2tp_ppp 모듈(PPPoL2TP)은 L2TP 세션 위에 PPP 인터페이스를 생성합니다.
사용자 공간의 pppd와 함께 동작하며, 전통적인 원격 접속 VPN에 사용됩니다.
/* l2tp_ppp.c: PPPoL2TP 소켓 패밀리 정의 */
static const struct proto_ops pppol2tp_ops = {
.family = AF_PPPOX,
.owner = THIS_MODULE,
.release = pppol2tp_release,
.bind = sock_no_bind,
.connect = pppol2tp_connect,
.getname = pppol2tp_getname,
.ioctl = pppol2tp_ioctl,
.sendmsg = pppol2tp_sendmsg,
.recvmsg = pppol2tp_recvmsg,
};
/* PPPoL2TP 소켓 주소 구조체 */
struct pppol2tp_addr {
__kernel_pid_t pid; /* pppd PID */
int fd; /* 터널 소켓 FD */
struct sockaddr_in addr; /* IP 주소 */
__u16 s_tunnel; /* 로컬 터널 ID */
__u16 s_session; /* 로컬 세션 ID */
__u16 d_tunnel; /* 원격 터널 ID */
__u16 d_session; /* 원격 세션 ID */
};
xl2tpd 설정 예제
; /etc/xl2tpd/xl2tpd.conf - LNS 설정
[global]
listen-addr = 0.0.0.0
port = 1701
access control = no
[lns default]
ip range = 10.10.10.128-10.10.10.254
local ip = 10.10.10.1
require chap = yes
refuse pap = yes
require authentication = yes
name = l2tp-lns
pppoptfile = /etc/ppp/options.xl2tpd
length bit = yes
# /etc/ppp/options.xl2tpd - PPP 옵션
ipcp-accept-local
ipcp-accept-remote
ms-dns 8.8.8.8
ms-dns 8.8.4.4
noccp
auth
mtu 1410
mru 1410
nodefaultroute
debug
lock
proxyarp
connect-delay 5000
Netlink 설정 인터페이스 (ip l2tp 명령)
리눅스 커널의 L2TP 서브시스템은 Generic Netlink를 통해 사용자 공간에서 터널과 세션을
동적으로 생성/삭제/조회할 수 있는 인터페이스를 제공합니다.
iproute2의 ip l2tp 명령이 이 인터페이스를 사용합니다.
Netlink 명령 체계
/* include/uapi/linux/l2tp.h: Netlink 명령 정의 */
enum {
L2TP_CMD_NOOP,
L2TP_CMD_TUNNEL_CREATE, /* 터널 생성 */
L2TP_CMD_TUNNEL_DELETE, /* 터널 삭제 */
L2TP_CMD_TUNNEL_MODIFY, /* 터널 수정 */
L2TP_CMD_TUNNEL_GET, /* 터널 조회 */
L2TP_CMD_SESSION_CREATE, /* 세션 생성 */
L2TP_CMD_SESSION_DELETE, /* 세션 삭제 */
L2TP_CMD_SESSION_MODIFY, /* 세션 수정 */
L2TP_CMD_SESSION_GET, /* 세션 조회 */
__L2TP_CMD_MAX,
};
/* Netlink 속성 정의 (주요 항목) */
enum {
L2TP_ATTR_NONE,
L2TP_ATTR_PW_TYPE, /* pseudowire 유형 */
L2TP_ATTR_ENCAP_TYPE, /* 캡슐화 유형 (UDP/IP) */
L2TP_ATTR_PROTO_VERSION, /* 프로토콜 버전 (2/3) */
L2TP_ATTR_CONN_ID, /* 로컬 터널 ID */
L2TP_ATTR_PEER_CONN_ID, /* 원격 터널 ID */
L2TP_ATTR_SESSION_ID, /* 로컬 세션 ID */
L2TP_ATTR_PEER_SESSION_ID, /* 원격 세션 ID */
L2TP_ATTR_UDP_CSUM, /* UDP 체크섬 사용 */
L2TP_ATTR_COOKIE, /* 세션 쿠키 */
L2TP_ATTR_PEER_COOKIE, /* 원격 쿠키 */
/* ... */
};
ip l2tp 명령 예제
# L2TPv3 over UDP 터널 생성
ip l2tp add tunnel \
tunnel_id 100 peer_tunnel_id 200 \
encap udp \
local 192.168.1.1 remote 192.168.1.2 \
udp_sport 5000 udp_dport 5000
# 이더넷 세션 생성
ip l2tp add session \
tunnel_id 100 \
session_id 1000 peer_session_id 2000 \
cookie 0011223344556677 peer_cookie 7766554433221100
# 생성된 l2tpeth 인터페이스 활성화
ip link set l2tpeth0 up
ip addr add 10.0.0.1/24 dev l2tpeth0
# 터널/세션 목록 조회
ip l2tp show tunnel
ip l2tp show session
# 터널/세션 삭제
ip l2tp del session tunnel_id 100 session_id 1000
ip l2tp del tunnel tunnel_id 100
커널 설정 (CONFIG_L2TP, CONFIG_L2TP_V3, CONFIG_L2TP_ETH)
관련 커널 설정 옵션
# 핵심 L2TP 지원
CONFIG_L2TP=m # L2TP 코어 (l2tp_core)
CONFIG_L2TP_V3=y # L2TPv3 지원 (l2tp_core에 포함)
# 세션 핸들러
CONFIG_L2TP_ETH=m # L2TPv3 이더넷 터널 (l2tp_eth)
CONFIG_PPPOL2TP=m # PPP over L2TP (l2tp_ppp)
# IP 캡슐화
CONFIG_L2TP_IP=m # L2TPv3 over IPv4 (l2tp_ip)
CONFIG_L2TP_DEBUGFS=m # debugfs 디버그 인터페이스 (선택)
# 의존성
CONFIG_NET=y # 네트워킹 지원 (필수)
CONFIG_INET=y # IPv4 지원 (필수)
CONFIG_PPP=m # PPP 지원 (l2tp_ppp 사용 시)
CONFIG_PPPOX=m # PPPoX 소켓 (l2tp_ppp 사용 시)
CONFIG_IPV6=m # IPv6 지원 (l2tp_ip6 사용 시)
모듈 로딩 및 의존성
# 모듈 수동 로딩
modprobe l2tp_core
modprobe l2tp_netlink
modprobe l2tp_eth # 이더넷 터널 사용 시
modprobe l2tp_ppp # PPP 터널 사용 시
modprobe l2tp_ip # IP 캡슐화 사용 시
# 모듈 의존성 확인
modinfo l2tp_eth
# depends: l2tp_core,l2tp_netlink
# 현재 로딩된 L2TP 모듈 확인
lsmod | grep l2tp
ip l2tp 명령 사용 시 필요한 모듈이 자동으로 로딩됩니다.
Netlink Generic Family 이름(l2tp)으로 l2tp_netlink가 먼저 로딩되고,
세션 유형에 따라 l2tp_eth 또는 l2tp_ppp가 추가 로딩됩니다.
보안과 성능
IPsec 연동
L2TP 자체에는 암호화(Encryption) 기능이 없으므로, 보안이 필요한 환경에서는 반드시 IPsec(특히 ESP 터널/트랜스포트 모드)과 함께 사용해야 합니다. 일반적으로 L2TP/IPsec이라 불리는 조합은 IKE(포트 500/4500)로 SA를 수립한 후, L2TP 트래픽을 ESP로 암호화합니다.
strongSwan 연동 설정
# /etc/ipsec.conf - strongSwan L2TP/IPsec 설정
conn l2tp-psk
authby=secret
type=transport
left=%defaultroute
leftprotoport=17/1701
right=%any
rightprotoport=17/%any
keyexchange=ikev1
ike=aes256-sha256-modp2048
esp=aes256-sha256
auto=add
rekey=no
UDP 캡슐화 (NAT Traversal)
NAT 환경에서 IPsec ESP 패킷이 차단되는 문제를 해결하기 위해,
ESP를 UDP 포트 4500으로 캡슐화하는 NAT-T(NAT Traversal)를 사용합니다.
커널은 XFRM 프레임워크를 통해 이를 자동으로 처리합니다.
성능 고려사항
| 항목 | 영향 | 최적화 방법 |
|---|---|---|
| 캡슐화 오버헤드 | 패킷당 20-60바이트 추가 | MTU 조정, Path MTU Discovery 활성화 |
| 암호화 부하 | IPsec ESP 처리 CPU 사용 | AES-NI 하드웨어 가속 활용 |
| 이중 캡슐화 | L2TP + IPsec 중첩 오버헤드 | L2TPv3 over IP 사용 (NAT 불필요 시) |
| 세그먼테이션 오프로드 | 터널 내부 TSO/GSO 미동작 가능 | GRO 활성화, 터널 디바이스 GSO 확인 |
실전 구성 예제
예제 1: L2TPv3 이더넷 브리징 (사이트 간 L2 연결)
# === 사이트 A (192.168.1.1) ===
# L2TPv3 over UDP 터널 생성
ip l2tp add tunnel \
tunnel_id 1 peer_tunnel_id 2 \
encap udp \
local 192.168.1.1 remote 192.168.2.1 \
udp_sport 5000 udp_dport 5000
# 이더넷 세션 생성
ip l2tp add session \
tunnel_id 1 \
session_id 10 peer_session_id 20
# l2tpeth0 인터페이스 활성화
ip link set l2tpeth0 up mtu 1446
# 브리지 생성 및 인터페이스 추가
ip link add br0 type bridge
ip link set eth1 master br0 # 로컬 LAN 인터페이스
ip link set l2tpeth0 master br0 # L2TP 가상 인터페이스
ip link set br0 up
ip addr add 10.0.0.1/24 dev br0
# === 사이트 B (192.168.2.1) ===
# L2TPv3 over UDP 터널 생성 (ID 반대)
ip l2tp add tunnel \
tunnel_id 2 peer_tunnel_id 1 \
encap udp \
local 192.168.2.1 remote 192.168.1.1 \
udp_sport 5000 udp_dport 5000
# 이더넷 세션 생성 (ID 반대)
ip l2tp add session \
tunnel_id 2 \
session_id 20 peer_session_id 10
# l2tpeth0 인터페이스 활성화
ip link set l2tpeth0 up mtu 1446
# 브리지 생성 및 인터페이스 추가
ip link add br0 type bridge
ip link set eth1 master br0
ip link set l2tpeth0 master br0
ip link set br0 up
ip addr add 10.0.0.2/24 dev br0
예제 2: L2TPv2 LAC-LNS 구성 (PPP VPN)
# LNS 서버 - xl2tpd 시작
systemctl start xl2tpd
# LAC 클라이언트 - xl2tpd 설정
cat << 'EOF' > /etc/xl2tpd/xl2tpd.conf
[global]
access control = no
[lac vpn-server]
lns = 203.0.113.1
pppoptfile = /etc/ppp/options.l2tpd.client
length bit = yes
redial = yes
redial timeout = 5
max redials = 5
EOF
# LAC 연결 시작
echo "c vpn-server" > /var/run/xl2tpd/l2tp-control
# 연결 확인
ip addr show ppp0
ip route show
MTU 계산
- L2TPv3/UDP: 1500 - 20(IP) - 8(UDP) - 12(L2TP) - 14(inner Eth) = 1446
- L2TPv3/IP: 1500 - 20(IP) - 4(L2TP) - 14(inner Eth) = 1462
- L2TP/IPsec: 약 1300-1380 (ESP + IV + 패딩(Padding) + auth 포함)
예제 3: L2TPv3 over IP (Proto 115) 전용 구성
이 예제는 UDP를 사용하지 않고 IP 프로토콜 번호 115로 직접 L2TPv3를 전달합니다. NAT가 없는 전용망/DC 내부 백본에서 오버헤드를 줄이고 지연(Latency) 편차를 최소화할 때 유용합니다. 반대로 NAT/인터넷 경로에서는 권장되지 않습니다.
# === Site A ===
ip l2tp add tunnel \
tunnel_id 100 peer_tunnel_id 200 \
encap ip \
local 10.10.10.1 remote 10.10.10.2 \
version 3
ip l2tp add session \
tunnel_id 100 \
session_id 1000 peer_session_id 2000 \
cookie 0x11112222 peer_cookie 0x33334444 \
l2spec_type none
ip link set l2tpeth0 up mtu 1462
ip link add br0 type bridge
ip link set eth1 master br0
ip link set l2tpeth0 master br0
ip link set br0 up
# === Site B ===
ip l2tp add tunnel \
tunnel_id 200 peer_tunnel_id 100 \
encap ip \
local 10.10.10.2 remote 10.10.10.1 \
version 3
ip l2tp add session \
tunnel_id 200 \
session_id 2000 peer_session_id 1000 \
cookie 0x33334444 peer_cookie 0x11112222 \
l2spec_type none
ip link set l2tpeth0 up mtu 1462
ip link add br0 type bridge
ip link set eth1 master br0
ip link set l2tpeth0 master br0
ip link set br0 up
# 검증 절차
ip l2tp show tunnel
ip l2tp show session
ip -s link show l2tpeth0
tcpdump -ni eth0 proto 115
# 실패 시 점검
# 1) 방화벽이 proto 115 차단
# 2) session_id / peer_session_id 교차 불일치
# 3) cookie / peer_cookie 불일치
# 4) MTU 과대 설정으로 단편화/손실 발생
제어 채널 상태 기계와 재전송
L2TP 데이터 채널은 best-effort로 동작하지만, 제어 채널은 Ns/Nr 순서 번호와 재전송 타이머(Timer)로 신뢰성을 보장합니다.
현장에서 자주 발생하는 문제는 "데이터는 일부 보이는데 세션이 붙지 않음"으로, 대부분 제어 메시지 교환 실패(SCCRQ/SCCRP/ICRQ/ICRP 구간)에서 발생합니다.
L2TPv2 제어 메시지 핸드셰이크
| 단계 | 메시지 | 역할 | 실패 시 증상 |
|---|---|---|---|
| 1 | SCCRQ | 터널 연결 요청 시작 | 상대가 응답하지 않음 |
| 2 | SCCRP | 터널 연결 응답 (Tunnel ID 제시) | 터널 ID 충돌/거부 |
| 3 | SCCCN | 제어 연결 확정 | 제어 연결 수립 직전 종료 |
| 4 | ICRQ/ICRP/ICCN | 세션(호출) 생성 | ppp 인터페이스가 생기지 않음 |
| 5 | SLI/ZLB ACK | 링크 상태/응답 확인 | 주기적 재전송 증가 |
| 종료 | CDN/StopCCN | 세션/터널 해제 | 고아 세션 누적 |
재전송과 타이머 튜닝 포인트
# 운영 관찰 포인트 (도구별)
- xl2tpd: retransmit 횟수, hello timeout, control connection restart
- pppd: LCP Echo-Request/Echo-Reply 지연
- 커널: l2tp_core debug=0x0f 에서 제어 메시지 왕복 추적
# 장애 징후
- SCCRQ만 반복되고 SCCRP 없음: 방화벽/포트 정책 문제
- SCCRP 후 SCCCN 없음: AVP 협상 불일치(인증/프로파일)
- 세션 수립 후 CDN 빈발: MTU/MRU 불일치 또는 PPP LCP 협상 실패
NAT/방화벽/IPsec 설계 포인트
L2TP 단독은 암호화를 제공하지 않으므로 실제 운영에서는 L2TP/IPsec 조합이 일반적입니다. 특히 NAT 구간이 하나라도 있으면 L2TPv3 over IP(proto 115)보다 UDP 캡슐화가 현실적으로 유리합니다.
전송 방식별 네트워크 적합성
| 방식 | 장점 | 제약 | 권장 환경 |
|---|---|---|---|
| L2TPv3 over UDP | NAT 통과 용이, 운영 단순 | UDP 헤더 오버헤드 | 인터넷, 멀티 NAT |
| L2TPv3 over IP(115) | 헤더 오버헤드 최소 | NAT/방화벽 통과 어려움 | 전용망, 폐쇄망 |
| L2TP/IPsec (UDP 1701 + ESP) | 기밀성/무결성(Integrity) 확보 | IKE/정책 운영 복잡성 | 원격 접속 VPN |
| L2TP/IPsec NAT-T (UDP 4500) | NAT 환경에서도 IPsec 가능 | 캡슐화 단계 증가 | 클라우드/재택 환경 |
운영 방화벽 체크리스트
# nftables 예시 (IPv4/IPv6 공통 정책은 환경 맞게 확장)
nft add rule inet filter input udp dport { 500, 1701, 4500 } accept
nft add rule inet filter input ip protocol esp accept
# conntrack 확인: NAT-T 흐름 확인
conntrack -L -p udp | grep -E 'dport=(500|1701|4500)'
# xfrm 정책/상태 확인
ip xfrm state
ip xfrm policy
대규모 운영과 고가용성 설계
수백~수천 세션 환경에서는 단일 노드의 CPU, softirq, 메모리 단편화(Fragmentation), conntrack 용량이 병목(Bottleneck)이 됩니다. L2TP는 세션이 많아질수록 제어 채널 안정성과 데이터 경로 분산(IRQ/RPS/XPS)이 성능의 핵심입니다.
규모별 운영 모델
| 규모 | 권장 구조 | 핵심 튜닝 포인트 |
|---|---|---|
| 소규모 (< 100 세션) | 단일 LNS | MTU/keepalive 정합성 |
| 중규모 (100~1000 세션) | LNS + 전용 모니터링 | RPS/XPS, conntrack, IRQ 분산 |
| 대규모 (1000+ 세션) | 다중 LNS 풀 + Anycast/LB | 세션 스티키 정책, 장애 전이 시간 |
| 이중화 | active-standby 또는 active-active | 터널 재수립 시간과 라우팅 수렴 시간 분리 측정 |
커널/시스템 튜닝 예시
# 소켓/큐 용량 (환경별 검증 후 적용)
sysctl -w net.core.rmem_max=33554432
sysctl -w net.core.wmem_max=33554432
sysctl -w net.core.netdev_max_backlog=100000
# conntrack 여유 확보 (NAT 환경)
sysctl -w net.netfilter.nf_conntrack_max=1048576
# UDP 메모리 임계값
sysctl -w net.ipv4.udp_mem='262144 524288 1048576'
# NIC 큐/IRQ 확인
ethtool -l eth0
cat /proc/interrupts | grep -E 'eth0|mlx|ixgbe'
디버깅과 모니터링
커널 디버그 메시지 활성화
# L2TP 디버그 플래그 활성화 (비트마스크)
# 0x01: 터널 제어 0x02: 터널 데이터
# 0x04: 세션 제어 0x08: 세션 데이터
echo 0x0f > /sys/module/l2tp_core/parameters/debug
# 또는 모듈 로딩 시
modprobe l2tp_core debug=0x0f
# 커널 로그 확인
dmesg | grep l2tp
journalctl -k | grep l2tp
debugfs 인터페이스
# debugfs 마운트 (이미 마운트되지 않은 경우)
mount -t debugfs none /sys/kernel/debug
# L2TP 터널 정보 확인
cat /sys/kernel/debug/l2tp/tunnels
# 출력 예:
# Tunnel 1, encap UDP, ver 3, fd 8
# peer tunnel 2
# from 192.168.1.1:5000 to 192.168.2.1:5000
# Session 10 (peer 20)
# interface l2tpeth0
# TX: 12345 bytes 67 packets
# RX: 54321 bytes 89 packets
tcpdump로 L2TP 패킷 캡처
# L2TP over UDP 캡처 (포트 1701)
tcpdump -i eth0 -nn udp port 1701 -vv
# L2TP over IP 캡처 (프로토콜 115)
tcpdump -i eth0 -nn proto 115 -vv
# L2TP/IPsec 캡처 (ESP + IKE)
tcpdump -i eth0 -nn '(udp port 500 or udp port 4500 or esp)' -vv
# 터널 내부 패킷 캡처 (l2tpeth 인터페이스에서)
tcpdump -i l2tpeth0 -nn -e
통계 및 모니터링
# ip l2tp 통계 확인
ip l2tp show tunnel
ip l2tp show session
# 인터페이스 통계
ip -s link show l2tpeth0
# 네트워크 네임스페이스 내 L2TP 확인
ip netns exec ns1 ip l2tp show tunnel
# 소켓 상태 확인
ss -unp | grep l2tp
일반적인 문제 해결
| 증상 | 원인 | 해결 방법 |
|---|---|---|
| 터널 생성 실패 | 모듈 미로딩 또는 소켓 바인딩 실패 | modprobe l2tp_netlink, 포트 충돌 확인 |
| 패킷 손실 | MTU 불일치로 단편화 발생 | 양쪽 MTU 동일하게 조정, PMTUD 확인 |
| l2tpeth0 생성 안 됨 | l2tp_eth 모듈 미로딩 | modprobe l2tp_eth |
| 세션 ID 불일치 | 양쪽 session_id/peer_session_id가 교차 설정되지 않음 | A의 session_id = B의 peer_session_id 확인 |
| IPsec 연동 안 됨 | IKE SA 수립 실패 또는 정책 불일치 | ipsec statusall, xfrm 정책 확인 |
| NAT 환경에서 연결 불가 | IP 캡슐화 사용 중 (NAT 미지원) | UDP 캡슐화로 전환 |
ftrace/perf/eBPF 추적
패킷 손실이나 지연 급증을 "증상"이 아니라 "경로 지연"으로 분해하려면 커널 이벤트 추적이 필요합니다. L2TP는 수신 softirq 경로와 세션 디캡슐화 경로가 분리되므로, NIC IRQ부터 L2TP 세션 전달까지 단계별 시간을 측정해야 합니다.
# 1) 네트워크 수신 경로 기본 이벤트
trace-cmd record \
-e napi:napi_poll \
-e net:netif_receive_skb \
-e skb:kfree_skb
# 2) 소프트IRQ 지연 관찰
trace-cmd record -e irq:softirq_entry -e irq:softirq_exit -e irq:irq_handler_entry -e irq:irq_handler_exit
# 3) 결과 확인
trace-cmd report | less
# 4) perf로 CPU 핫스팟 확인
perf top -g --call-graph dwarf
perf record -g -a -- sleep 20
perf report
# bpftrace 예시: UDP 1701 수신량/지연 경향 관찰
bpftrace -e '
kprobe:udp_queue_rcv_skb /((struct sock *)arg0)->__sk_common.skc_dport == htons(1701)/ {
@rx++;
}
interval:s:5 {
printf("udp1701_rx=%d\\n", @rx);
clear(@rx);
}'
# 주의: 커널 버전별 심볼/구조체가 다르므로 환경 맞춤 수정 필요
운영 점검 체크리스트 (배포 전/장애 후 공통)
| 항목 | 확인 명령 | 정상 기준 |
|---|---|---|
| 터널/세션 수 | ip l2tp show tunnel, ip l2tp show session | 기대 세션 수와 일치 |
| MTU/MRU | ip link show l2tpeth0, ppp 옵션 | 양 끝단 값 일치 |
| 재전송 증가 | xl2tpd 로그, dmesg | grep l2tp | 지속 증가 없음 |
| NAT 상태 | conntrack -L | 주기적 만료/재생성 과다 없음 |
| IPsec SA | ip xfrm state | replay/error 카운터 안정 |
| CPU softirq | mpstat -P ALL 1, top -H | 특정 코어 과점유 없음 |
L2TPv3 Pseudowire 유형별 상세와 L3VPN 통합
L2TPv3의 핵심 기능은 pseudowire(PW)를 통해 다양한 Layer 2 프로토콜을
IP 네트워크 위에서 투명하게 전달하는 것입니다. 커널의 l2tp_session 구조체에서
pwtype 필드가 pseudowire 유형을 결정하며, 각 유형에 따라 캡슐화/역캡슐화 동작이
달라집니다.
Pseudowire 유형 분류
| PW 유형 | L2TP_PWTYPE 상수 | RFC | 용도 | 커널 모듈 |
|---|---|---|---|---|
| Ethernet | L2TP_PWTYPE_ETH (0x0005) | RFC 4719 | L2 브리징, VPLS | l2tp_eth |
| Ethernet VLAN | L2TP_PWTYPE_ETH_VLAN (0x0004) | RFC 4719 | VLAN 태그 보존 전달 | l2tp_eth |
| PPP | L2TP_PWTYPE_PPP (0x0007) | RFC 2661 | 원격 접속 VPN | l2tp_ppp |
| HDLC | L2TP_PWTYPE_PPP_AC (0x0001) | RFC 4349 | 시리얼 WAN 연결 | 사용자 공간 |
| Frame Relay | L2TP_PWTYPE_IP (0x000B) | RFC 4591 | 레거시 FR 마이그레이션 | 사용자 공간 |
Ethernet PW의 내부 동작
Ethernet pseudowire(L2TP_PWTYPE_ETH)는 가장 널리 사용되는 유형으로,
원본 이더넷 프레임 전체(MAC 헤더 포함)를 L2TP 세션 안에 캡슐화합니다.
수신 측에서는 L2TP 헤더를 제거한 후 l2tpethN 가상 인터페이스의
netif_rx()를 통해 커널 네트워크 스택에 전달합니다.
/* l2tp_eth.c: 이더넷 프레임 수신 처리 */
static void l2tp_eth_dev_recv(struct l2tp_session *session,
struct sk_buff *skb, int data_len)
{
struct l2tp_eth_sess *spriv = l2tp_session_priv(session);
struct net_device *dev;
/* L2TP 헤더 제거, 이더넷 프레임만 남김 */
skb_pull(skb, data_len);
dev = spriv->dev;
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
/* 통계 업데이트 */
dev_sw_netstats_rx_add(dev, skb->len);
/* 커널 네트워크 스택으로 전달 (브리지/라우팅 처리) */
netif_rx(skb);
}
L3VPN 구성 (L2TPv3 + IP 라우팅)
L2TPv3 이더넷 pseudowire를 사용하면 L2 수준의 연결이 가능하지만,
대규모 환경에서는 L3VPN 방식이 더 효율적입니다.
l2tpeth 인터페이스에 IP 주소를 직접 할당하고 라우팅으로 트래픽을 제어하면
브로드캐스트 도메인을 분리하면서도 사이트 간 IP 통신이 가능합니다.
# L3VPN 모드: l2tpeth에 직접 IP 주소 할당 (브리지 없이)
ip l2tp add tunnel tunnel_id 300 peer_tunnel_id 400 \
encap udp local 10.1.1.1 remote 10.1.1.2 \
udp_sport 5000 udp_dport 5000
ip l2tp add session tunnel_id 300 \
session_id 3000 peer_session_id 4000
# 브리지 없이 직접 라우팅
ip link set l2tpeth0 up mtu 1446
ip addr add 172.16.0.1/30 dev l2tpeth0
# 원격 사이트의 서브넷으로 정적 라우팅
ip route add 192.168.100.0/24 via 172.16.0.2 dev l2tpeth0
ip route add 192.168.200.0/24 via 172.16.0.2 dev l2tpeth0
- L2VPN (브리지 모드): 동일 서브넷 공유 필요, ARP/DHCP 투명 전달, 소규모 사이트
- L3VPN (라우팅 모드): 서브넷 분리, 브로드캐스트 격리(Isolation), 대규모/멀티사이트
xfrm/IPsec 연동
L2TP/IPsec 조합에서 커널의 xfrm 프레임워크는 SA(Security Association)와
SP(Security Policy)를 관리합니다. ESP(Encapsulating Security Payload)가
L2TP 트래픽을 암호화하며, NAT 환경에서는 NAT-T(NAT Traversal)를 통해
ESP를 UDP 4500으로 캡슐화합니다.
IPsec + L2TP 패킷 중첩 구조
Transport/Tunnel 모드 조합별 SA/SP 설정
# Transport 모드: L2TP 트래픽(UDP 1701)만 ESP로 암호화
ip xfrm state add \
src 192.168.1.1 dst 203.0.113.1 \
proto esp spi 0x12345678 reqid 1 \
mode transport \
enc 'cbc(aes)' 0x$(openssl rand -hex 32) \
auth 'hmac(sha256)' 0x$(openssl rand -hex 32)
ip xfrm policy add \
src 192.168.1.1 dst 203.0.113.1 \
proto udp sport 1701 \
dir out \
tmpl src 192.168.1.1 dst 203.0.113.1 \
proto esp reqid 1 mode transport
# Tunnel 모드: 사이트 간 전체 L2TP 트래픽 암호화
ip xfrm state add \
src 10.0.0.1 dst 10.0.0.2 \
proto esp spi 0xABCD1234 reqid 2 \
mode tunnel \
enc 'cbc(aes)' 0x$(openssl rand -hex 32) \
auth 'hmac(sha256)' 0x$(openssl rand -hex 32)
ip xfrm policy add \
src 10.0.0.0/24 dst 10.0.1.0/24 \
dir out \
tmpl src 10.0.0.1 dst 10.0.0.2 \
proto esp reqid 2 mode tunnel
NAT-T 자동 감지 흐름
IKE(Internet Key Exchange) 협상 단계에서 NAT 감지(NAT Detection)가 이루어집니다.
양쪽 IKE 피어가 NAT-D(NAT Detection) 페이로드를 교환하여 경로에 NAT가 있는지 확인하고,
NAT가 감지되면 자동으로 ESP를 UDP 4500으로 캡슐화합니다.
커널의 xfrm 프레임워크가 encap 속성을 통해 이를 처리합니다.
/* xfrm_state에 NAT-T 캡슐화 설정 */
struct xfrm_encap_tmpl {
__u16 encap_type; /* UDP_ENCAP_ESPINUDP */
__be16 encap_sport; /* 로컬 포트 (4500) */
__be16 encap_dport; /* 원격 포트 (4500) */
xfrm_address_t encap_oa; /* 원본 주소 (NAT 전) */
};
커널 트레이싱
L2TP 서브시스템의 성능 문제나 패킷 손실을 정밀 분석하려면 커널 수준의 트레이싱이 필수입니다. ftrace tracepoint, perf probe, BPF 프로그램을 활용하여 패킷 경로의 각 단계별 지연과 드롭 지점을 정확히 파악할 수 있습니다.
ftrace L2TP 관련 이벤트
# L2TP 관련 커널 함수 목록 확인
cat /sys/kernel/debug/tracing/available_filter_functions | grep l2tp
# function_graph 트레이서로 L2TP 수신 경로 추적
echo function_graph > /sys/kernel/debug/tracing/current_tracer
echo 'l2tp_udp_encap_recv' > /sys/kernel/debug/tracing/set_graph_function
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 일정 시간 후 트레이스 확인
cat /sys/kernel/debug/tracing/trace
# 종료
echo 0 > /sys/kernel/debug/tracing/tracing_on
echo nop > /sys/kernel/debug/tracing/current_tracer
perf probe를 이용한 동적 트레이싱
# l2tp_recv_common 진입점에 프로브 등록
perf probe -a 'l2tp_recv_common session=%di:x64'
# l2tp_xmit_skb 송신 경로 프로브
perf probe -a 'l2tp_xmit_skb session=%di:x64 skb=%si:x64'
# 프로브 기반 기록 수집
perf record -e 'probe:l2tp_recv_common' \
-e 'probe:l2tp_xmit_skb' \
-a -- sleep 30
# 결과 분석
perf script | head -100
# 프로브 제거
perf probe -d 'l2tp_recv_common'
perf probe -d 'l2tp_xmit_skb'
BPF 기반 L2TP 세션별 통계 수집
# bpftrace: L2TP 세션별 수신 패킷 수와 바이트 집계
bpftrace -e '
kprobe:l2tp_recv_common {
@session_rx[arg0] = count();
}
kprobe:l2tp_xmit_skb {
@session_tx[arg0] = count();
}
interval:s:10 {
print(@session_rx);
print(@session_tx);
clear(@session_rx);
clear(@session_tx);
}'
# bpftrace: L2TP 수신 처리 지연 히스토그램
bpftrace -e '
kprobe:l2tp_udp_encap_recv {
@start[tid] = nsecs;
}
kretprobe:l2tp_udp_encap_recv /@start[tid]/ {
@latency_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
skb 드롭 추적
# 커널의 kfree_skb tracepoint로 L2TP 관련 드롭 추적
perf record -e skb:kfree_skb -a -- sleep 60
perf script | grep -i l2tp
# 또는 dropwatch 도구 활용
dropwatch -l kas
# 활성화 후 L2TP 터널 트래픽 발생시키면 드롭 위치 표시
L2TP 데이터 경로 최적화
대량의 L2TP 세션을 처리할 때 데이터 경로의 효율성이 전체 성능을 결정합니다. UDP encap 콜백 구조, GRO/GSO 지원 여부, 시퀀스 번호 처리 오버헤드가 핵심 요소입니다.
UDP encap 콜백 아키텍처
L2TP over UDP에서 패킷 수신은 일반 UDP 수신 경로를 통해 진행되다가,
udp_queue_rcv_skb() 단계에서 encap_rcv 콜백이 등록되어 있으면
L2TP 핸들러로 분기됩니다. 이 콜백 기반 구조 덕분에 L2TP 트래픽은 일반 UDP 소켓 처리 없이
바로 커널 내부에서 처리됩니다.
/* net/ipv4/udp.c: UDP encap 콜백 호출 경로 */
static int udp_queue_rcv_one_skb(struct sock *sk,
struct sk_buff *skb)
{
struct udp_sock *up = udp_sk(sk);
/* encap_rcv 콜백이 등록되어 있으면 L2TP 핸들러 호출 */
if (up->encap_type && up->encap_rcv) {
int ret = up->encap_rcv(sk, skb);
if (ret <= 0)
return -ret; /* L2TP가 처리 완료 */
}
/* fallback: 일반 UDP 소켓 큐에 전달 */
...
}
GRO/GSO 지원 현황
| 기능 | L2TP over UDP | L2TP over IP | 비고 |
|---|---|---|---|
| GRO (수신 병합) | UDP GRO 경유 지원 | 미지원 | 커널 5.0+ UDP GRO |
| GSO (송신 분할) | UDP GSO 경유 가능 | 제한적 | l2tp_xmit_skb에서 segmentation 처리 |
| TSO | 터널 내부 미동작 | 미동작 | NIC HW offload 한계 |
| Checksum offload | UDP csum offload 가능 | IP csum만 | ethtool -K로 확인 |
시퀀스 번호 처리 성능
L2TP 데이터 메시지에서 시퀀스 번호(Ns/Nr)는 선택 사항이지만,
활성화하면 패킷 순서 보장(Ordering)과 중복 감지가 가능합니다. 다만 고속 환경에서는
시퀀스 번호 처리가 성능 오버헤드를 유발할 수 있습니다.
# 시퀀스 번호 비활성화 (성능 우선)
ip l2tp add session tunnel_id 1 \
session_id 100 peer_session_id 200 \
seq none
# 시퀀스 번호 수신만 체크 (절충안)
ip l2tp add session tunnel_id 1 \
session_id 100 peer_session_id 200 \
seq recv
# 시퀀스 번호 송수신 모두 활성화 (안정성 우선)
ip l2tp add session tunnel_id 1 \
session_id 100 peer_session_id 200 \
seq both
- 대역폭 중시 환경:
seq none+ GRO 활성화 + jumbo MTU - 안정성 중시 환경:
seq both+ cookie 활성화 + IPsec - 수신 큐 최적화:
net.core.netdev_budget=600으로 NAPI 처리량(Throughput) 증가
멀티세션 아키텍처
L2TP의 핵심 설계 원칙 중 하나는 단일 터널 위에 여러 세션을 다중화하는 것입니다. 커널은 세션 ID를 기반으로 한 해시 테이블을 사용하여 수신 패킷을 빠르게 올바른 세션으로 라우팅합니다.
세션 ID 해시 및 조회
/* l2tp_core.h: 세션 해시 테이블 크기 */
#define L2TP_HASH_BITS 4
#define L2TP_HASH_SIZE (1 << L2TP_HASH_BITS) /* = 16 버킷 */
/* l2tp_core.c: 세션 조회 (수신 경로에서 호출) */
struct l2tp_session *l2tp_tunnel_get_session(
struct l2tp_tunnel *tunnel, u32 session_id)
{
struct hlist_head *session_list;
struct l2tp_session *session;
session_list = l2tp_session_id_hash(tunnel, session_id);
hlist_for_each_entry_rcu(session, session_list, hlist) {
if (session->session_id == session_id) {
l2tp_session_inc_refcount(session);
return session;
}
}
return NULL;
}
/* 전역 세션 해시: 터널 없이 session_id만으로 조회 (L2TPv3) */
struct l2tp_session *l2tp_session_get(
struct net *net, u32 session_id)
{
struct l2tp_net *pn = l2tp_pernet(net);
struct hlist_head *session_list;
session_list = l2tp_session_id_hash_2(pn, session_id);
/* RCU 보호 하에 해시 순회 */
...
}
세션별 통계 구조
/* l2tp_core.h: 세션/터널 통계 구조체 */
struct l2tp_stats {
atomic_long_t tx_packets;
atomic_long_t tx_bytes;
atomic_long_t tx_errors;
atomic_long_t rx_packets;
atomic_long_t rx_bytes;
atomic_long_t rx_errors;
atomic_long_t rx_seq_discards; /* 순서 번호 불일치 드롭 */
atomic_long_t rx_oos_packets; /* out-of-sequence 패킷 */
atomic_long_t rx_cookie_discards; /* 쿠키 불일치 드롭 */
};
# 멀티세션 환경 통계 확인
ip l2tp show session
# 출력 예:
# Session 10 in tunnel 1
# Peer session 20, peer tunnel 2
# interface name: l2tpeth0
# offset 0, peer offset 0
# Using IP, 4 byte cookies
# Sequence: send=no recv=no
# Session 11 in tunnel 1
# Peer session 21, peer tunnel 2
# interface name: l2tpeth1
# ...
# 개별 세션 인터페이스 통계
for dev in l2tpeth0 l2tpeth1 l2tpeth2; do
echo "=== $dev ==="
ip -s link show $dev
done
L2TP + Bridge/VLAN 연동
l2tpeth 인터페이스는 일반 이더넷 디바이스와 동일한 인터페이스를 제공하므로,
리눅스 브리지의 포트로 추가하거나 802.1Q VLAN 하위 인터페이스를 생성할 수 있습니다.
이를 통해 L2TP 터널 위에 복잡한 Layer 2 토폴로지(Topology)를 구성할 수 있습니다.
VLAN-aware 브리지 + L2TP 구성
# VLAN-aware 브리지 생성
ip link add br0 type bridge vlan_filtering 1
ip link set br0 up
# L2TP 이더넷 인터페이스를 브리지에 추가
ip link set l2tpeth0 master br0
ip link set eth1 master br0
# VLAN 설정 (포트별 VLAN 할당)
bridge vlan add vid 100 dev l2tpeth0
bridge vlan add vid 200 dev l2tpeth0
bridge vlan add vid 100 dev eth1
bridge vlan add vid 200 dev eth1
# PVID(기본 VLAN) 설정
bridge vlan add vid 100 dev l2tpeth0 pvid untagged
bridge vlan add vid 100 dev eth1 pvid untagged
# VLAN 하위 인터페이스 생성 (관리 IP용)
ip link add link br0 name br0.100 type vlan id 100
ip addr add 10.100.0.1/24 dev br0.100
ip link set br0.100 up
ip link add link br0 name br0.200 type vlan id 200
ip addr add 10.200.0.1/24 dev br0.200
ip link set br0.200 up
# 확인
bridge vlan show
bridge fdb show br br0
STP(Spanning Tree) 고려사항
# STP 비활성화 (단일 링크)
ip link set br0 type bridge stp_state 0
# 또는 RSTP에서 L2TP 포트 비용 조정
ip link set l2tpeth0 type bridge_slave cost 1000
ISP/통신사 대규모 배포 아키텍처
ISP와 통신사에서 L2TP는 가입자 접속(Subscriber Access)의 핵심 프로토콜입니다. LAC(L2TP Access Concentrator)가 가입자의 PPP 세션을 수집하고, LNS(L2TP Network Server)에서 인증/정책 적용 후 인터넷이나 VPN으로 라우팅합니다. 대규모 환경에서는 RADIUS 연동과 BNG(Broadband Network Gateway) 아키텍처가 필수입니다.
RADIUS 연동 흐름
대규모 ISP 환경에서 LAC는 가입자의 PPP 인증 요청을 RADIUS 서버로 전달합니다. RADIUS 응답에 Tunnel-Type, Tunnel-Medium-Type, Tunnel-Server-Endpoint 등의 속성이 포함되면 LAC가 자동으로 해당 LNS에 L2TP 터널을 수립합니다.
주요 RADIUS 터널 속성
| RADIUS 속성 | 값 | 설명 |
|---|---|---|
| Tunnel-Type (64) | 3 (L2TP) | 터널 프로토콜 유형 |
| Tunnel-Medium-Type (65) | 1 (IPv4) | 터널 전송 매체 |
| Tunnel-Server-Endpoint (67) | LNS IP 주소 | 터널 종단점 |
| Tunnel-Password (69) | 공유 비밀 | 터널 인증용 비밀 |
| Tunnel-Assignment-Id (82) | 그룹 이름 | 터널 그룹 지정 |
| Tunnel-Preference (83) | 우선순위 | 다중 LNS 선택 순서 |
# FreeRADIUS users 파일 예시
subscriber1 Cleartext-Password := "pass123"
Tunnel-Type = L2TP,
Tunnel-Medium-Type = IPv4,
Tunnel-Server-Endpoint = "10.0.0.100",
Tunnel-Password = "l2tp-secret",
Tunnel-Assignment-Id = "corp-vpn",
Framed-IP-Address = 172.16.10.1,
Framed-IP-Netmask = 255.255.255.0
# 복수 LNS 로드밸런싱 (Tunnel-Preference 사용)
subscriber2 Cleartext-Password := "pass456"
Tunnel-Type:0 = L2TP,
Tunnel-Server-Endpoint:0 = "10.0.0.100",
Tunnel-Preference:0 = 1,
Tunnel-Type:1 = L2TP,
Tunnel-Server-Endpoint:1 = "10.0.0.101",
Tunnel-Preference:1 = 2
L2TPv2 vs L2TPv3 비교
L2TPv2와 L2TPv3는 같은 L2TP 계열이지만 설계 철학과 적용 범위가 크게 다릅니다. L2TPv2는 PPP 원격 접속에 특화되어 있고, L2TPv3는 범용 Layer 2 터널링을 위해 프로토콜 구조를 근본적으로 재설계했습니다.
프로토콜 차이 상세
| 구분 | L2TPv2 | L2TPv3 |
|---|---|---|
| 헤더 크기 | 6-20 바이트 (가변) | 4-16 바이트 (가변, 쿠키 포함) |
| 제어 채널 분리 | 동일 UDP 소켓에서 T비트로 구분 | 별도 제어 연결 가능 |
| IPv6 지원 | 미지원 | 완전 지원 (l2tp_ip6) |
| 다중 pseudowire | PPP만 | Ethernet, VLAN, PPP, HDLC, FR 등 |
| 터널 인증 | CHAP 기반 (선택) | 쿠키 + IPsec 권장 |
| 최대 세션 수 | 65535 (16비트) | 약 43억 (32비트) |
| 데이터 경로 효율 | PPP 프레임 오버헤드 | 이더넷 직접 전달 |
| 대규모 배포 | ISP 원격 접속 (감소 추세) | DC 간 L2VPN (증가 추세) |
마이그레이션 가이드: L2TPv2에서 L2TPv3로
- 기존 PPP 기반 세션이 이더넷 모드로 전환 가능한지 평가
- 양쪽 장비가 L2TPv3를 지원하는지 확인 (
CONFIG_L2TP_V3=y) - 터널 ID를 16비트에서 32비트로 확장 (기존 ID 유지 가능)
- 쿠키 인증 설정 추가 (보안 강화)
- IP 캡슐화 전환 시 방화벽에서 프로토콜 115 허용 확인
- 브리지 모드 전환 시 STP/VLAN 구성 계획 수립
- RADIUS 속성을 L2TPv3 호환으로 갱신
커널 소스 구조 상세
net/l2tp/ 디렉토리의 각 파일이 담당하는 역할과
주요 함수 호출 경로를 상세히 살펴봅니다. 소스 분석 시 이 구조를 먼저 파악하면
디버깅과 패치(Patch) 작성이 수월해집니다.
파일별 상세 역할
| 파일 | 줄 수 (6.x 기준) | 핵심 역할 | 주요 함수 |
|---|---|---|---|
l2tp_core.c | ~1800 | 터널/세션 생성/삭제, 캡슐화/역캡슐화, 통계 | l2tp_tunnel_create(), l2tp_session_create(), l2tp_recv_common(), l2tp_xmit_skb() |
l2tp_core.h | ~250 | 구조체 선언 (l2tp_tunnel, l2tp_session, l2tp_stats) | 매크로(Macro), 인라인 헬퍼 |
l2tp_netlink.c | ~900 | Generic Netlink 핸들러 (ip l2tp 명령 처리) | l2tp_nl_cmd_tunnel_create(), l2tp_nl_cmd_session_create() |
l2tp_ppp.c | ~1400 | PPPoL2TP 소켓, PPP 프레임 송수신 | pppol2tp_connect(), pppol2tp_sendmsg(), pppol2tp_recv() |
l2tp_eth.c | ~400 | L2TPv3 이더넷 가상 디바이스 (l2tpethN) | l2tp_eth_create(), l2tp_eth_dev_recv(), l2tp_eth_dev_xmit() |
l2tp_ip.c | ~700 | L2TPv3 over IPv4 (프로토콜 115) | l2tp_ip_recv(), l2tp_ip_sendmsg() |
l2tp_ip6.c | ~750 | L2TPv3 over IPv6 | l2tp_ip6_recv(), l2tp_ip6_sendmsg() |
l2tp_debugfs.c | ~300 | debugfs 진단 인터페이스 (/sys/kernel/debug/l2tp/) | l2tp_dfs_seq_show() |
주요 함수 호출 경로
/* === 수신 경로 (L2TP over UDP) === */
NIC IRQ -> napi_poll()
-> ip_rcv() -> ip_local_deliver()
-> udp_rcv() -> udp_queue_rcv_skb()
-> udp_queue_rcv_one_skb()
-> l2tp_udp_encap_recv() /* l2tp_core.c */
-> l2tp_recv_common() /* 헤더 파싱, 세션 조회 */
-> l2tp_session_get() /* 해시 테이블에서 세션 찾기 */
-> session->recv_skb() /* 콜백 호출 */
-> l2tp_eth_dev_recv() /* 이더넷 세션 */
-> netif_rx() /* 커널 네트워크 스택 전달 */
-> pppol2tp_recv() /* PPP 세션 */
-> ppp_input() /* PPP 프레임 처리 */
/* === 송신 경로 === */
dev_queue_xmit(l2tpethN)
-> l2tp_eth_dev_xmit() /* l2tp_eth.c */
-> l2tp_xmit_skb() /* l2tp_core.c: L2TP 헤더 추가 */
-> l2tp_xmit_core() /* 쿠키/시퀀스 번호 추가 */
-> ip_queue_xmit() / udp_sendmsg() /* IP/UDP 전송 */
/* === Netlink 설정 경로 === */
사용자: ip l2tp add tunnel ...
-> genl_rcv_msg()
-> l2tp_nl_cmd_tunnel_create() /* l2tp_netlink.c */
-> l2tp_tunnel_create() /* l2tp_core.c */
-> l2tp_tunnel_register() /* 소켓 바인딩, 전역 등록 */
사용자: ip l2tp add session ...
-> genl_rcv_msg()
-> l2tp_nl_cmd_session_create() /* l2tp_netlink.c */
-> l2tp_session_create() /* l2tp_core.c */
-> l2tp_eth_create() 또는 /* l2tp_eth.c (이더넷) */
pppol2tp_session_create() /* l2tp_ppp.c (PPP) */
RCU 보호 패턴
L2TP 서브시스템은 세션 조회 경로에서 rcu_read_lock()을 사용하여
락 경합(Contention) 없이 고속 패킷 처리를 달성합니다. 세션/터널 삭제 시에는
synchronize_rcu()를 통해 안전한 해제를 보장합니다.
/* 수신 경로에서의 RCU 보호 */
rcu_read_lock();
session = l2tp_tunnel_get_session(tunnel, session_id);
if (session) {
session->recv_skb(session, skb, data_len);
l2tp_session_dec_refcount(session);
}
rcu_read_unlock();
/* 세션 삭제 시 안전한 해제 */
hlist_del_init_rcu(&session->hlist);
synchronize_rcu();
l2tp_session_free(session);
네트워크 네임스페이스와 컨테이너 환경
L2TP 서브시스템은 네트워크 네임스페이스를 완전히 지원합니다.
각 네임스페이스는 독립적인 struct l2tp_net을 가지며,
터널/세션이 네임스페이스별로 격리됩니다. 이는 컨테이너 환경이나
멀티테넌트 VPN 서비스에서 필수적인 기능입니다.
# 네트워크 네임스페이스에서 L2TP 터널 생성
ip netns add tenant1
ip netns add tenant2
# veth 쌍으로 네임스페이스 연결
ip link add veth-t1 type veth peer name veth-t1-ns
ip link set veth-t1-ns netns tenant1
# 네임스페이스 내부에서 L2TP 구성
ip netns exec tenant1 ip l2tp add tunnel \
tunnel_id 1 peer_tunnel_id 2 \
encap udp local 10.0.1.1 remote 10.0.1.2 \
udp_sport 5000 udp_dport 5000
ip netns exec tenant1 ip l2tp add session \
tunnel_id 1 session_id 100 peer_session_id 200
# 격리 확인: 다른 네임스페이스에서는 보이지 않음
ip netns exec tenant2 ip l2tp show tunnel
# (출력 없음 - 터널이 tenant1에 격리됨)
/* l2tp_core.c: per-netns L2TP 데이터 */
struct l2tp_net {
struct list_head l2tp_tunnel_list;
/* 이 네임스페이스의 터널 목록 */
struct hlist_head l2tp_session_hlist[L2TP_HASH_SIZE];
/* 전역 세션 해시 */
spinlock_t l2tp_tunnel_list_lock;
struct dentry *l2tp_dentry;
/* debugfs 루트 엔트리 */
};
/* 네트워크 네임스페이스 초기화/정리 */
static struct pernet_operations l2tp_net_ops = {
.init = l2tp_init_net,
.exit = l2tp_exit_net,
.id = &l2tp_net_id,
.size = sizeof(struct l2tp_net),
};
참고자료
- RFC 2661 - Layer Two Tunneling Protocol "L2TP" (L2TPv2 표준)
- RFC 3931 - Layer Two Tunneling Protocol - Version 3 (L2TPv3)
- RFC 3193 - Securing L2TP using IPsec
- RFC 4719 - Transport of Ethernet Frames over L2TPv3
- Linux Kernel Documentation - L2TP
- 커널 소스: net/l2tp/
- ip-l2tp(8) 매뉴얼 페이지(Page)
- xl2tpd - L2TP 데몬
- RFC 3308 - Layer Two Tunneling Protocol (L2TP) Differentiated Services Extension — L2TP DiffServ 확장 표준입니다
- RFC 4951 - Fail Over Extensions for Layer 2 Tunneling Protocol (L2TP) "failover" — L2TP 장애 복구 확장을 정의합니다
- RFC 5571 - Softwire Hub and Spoke Deployment Framework with L2TPv2 — L2TPv2 기반 소프트와이어 배치 프레임워크입니다
- 커널 소스: net/l2tp/l2tp_core.c — L2TP 핵심 구현 소스 코드입니다
- 커널 소스: net/l2tp/l2tp_netlink.c — L2TP Netlink 인터페이스 구현 소스 코드입니다
- ip-l2tp(8) man page (상세) — L2TP 터널 및 세션 생성/삭제 명령어의 전체 옵션을 설명합니다
- GRE (Generic Routing Encapsulation) - 또 다른 터널링 프로토콜
- IPSec & xfrm - L2TP 보안 연동의 핵심
- WireGuard - 현대적인 VPN 프로토콜
- TUN/TAP - 가상 네트워크 디바이스
- Bridge/VLAN/Bonding - L2TP 이더넷 브리징의 기반