# VPP 与 L2TP ## L2TP 介绍 Layer Two Tunneling Protocol 第二层隧道协议,是一种虚拟隧道协议,通常用于虚拟专用网。 L2TP 是一种工业标准的 Internet 隧道协议。 ### 一、通信过程:( rfc2661 ) #### 1、隧道建立流程: ![establishment](../../_static/vpp_l2tp_establishment.png) 1)、SCCRQ Start-Control-Connection-Request : 控制连接发起请求,由 LAC 或 LNS 向对端发送,用来初始化 LAC 和 LNS 之间的 tunnel ,开始 tunnel 的建立过程。【协商隧道 ID 、隧道认证等】 2)、SCCRP Start-Control-Connection-Reply : 表示接受了对端的连接请求, tunnel 的建立过程可以继续。 3)、SCCCN Start-Control-Connection-Connected : 对 SCRRP 的回应,完成 tunnel 的建立。【隧道由隧道 ID 标识】 4)、ZLB : 零长度消息报文,一般为查询报文, LAC 可以用 Hello 报文进行恢复,也可以直接丢弃。 #### 2、会话建立流程:【协商会话 ID , 会话中携带了 LAC 的 LCP 协商信息和用户认证】 5)、ICRQ Incoming-Call-Requst : 当 LAC 检测到有用户拨入电话的时候,向 LNS 发 ICRQ ,请求在已建立的 tunnel 中建立 session 。 6)、ICRP Incoming-Call-Reply : 用来回应 ICRQ ,表示 ICRQ 成功, LNS 也会可在 ICRP 中标识 L2TP session 必要的参数。 7)、ICCN Incoming-Call-Connected : 用来回应 ICRP , L2TP session建立完成。 #### 3、隧道拆除流程: ![finish](../../_static/vpp_l2tp_finish.png) 1)、发起端通过发送 StopCCN 消息报文到对端来通知对端拆除控制连接。 2)、对端收到后发送 ZLB ACK 消息作为回应,同时在一定时间内保持控制连接以防止 ZLB ACK 消息丢失。 #### 4、“隧道”和“会话" : 为了在 VPN 用户和服务器之间传递数据报文,必须在 LAC 和 LNS 之间建立传递数据报文的隧道和会话连接。 隧道是保证具有相同会话连接特性的一组用户可以共享的连接属性所定义的通道,而会话是针对每个用户与企业 VPN 服务器建立连接的 PPP 数据通道。 同一对 LAC 与 LNS 之间只可建立一个 L2TP 隧道,多个会话复用在一个隧道连接上。隧道和会话是动态建立与删除的。 会话的建立是由 PPP 模块触发,如果该会话在建立时没有可用的隧道,那么先建立隧道连接。会话建立完毕后,开始进行数据传输。 隧道建立后,一直要等到该隧道所属会话全部下线后才能进行拆除,为了确认对端的隧道依然存在,需要定时发送维护报文,其流程未 LAC 或 LNS 发出 Hello 报文进行查询,对应的 LNS 或 LAC 发出 ZLB 进行确认。 ZLB 报文只有一个 L2TP 的报文头。 ### 二、协议结构: #### 1、协议栈: ![protocol](../../_static/vpp_l2tp_protocol.png) #### 2、报文格式: 分控制消息和数据消息两种: ![struct](../../_static/vpp_l2tp_pkg_struct.jpg) ![struct](../../_static/vpp_l2tp_pkg_struct.png) #### 3、通信过程报文格式: ![record](../../_static/vpp_l2tp_records.png) ## VPP 中的 l2tp 实现: #### 1、L2TP 模块命令行: ```bash show l2tpv3 [verbose] # 查看 L2TP session test l2tp counters # 测试 L2TP 计数器 clear l2tp counters # 清除 L2TP 计数器 create l2tpv3 tunnel # 创建 L2TP 隧道 set l2tpv3 tunnel cookie # 设置隧道 cookie set interface ip6 l2tpv3 # 设置 L2TPV3 支持 IP6 转发 ``` 注: 1)创建隧道并非 RFC 3931 -- 3.4.1 中提到的三个阶段,在阅读代码也发现,这块主要是实现虚拟网卡生成、注册,且必须使用 bridge-domain 关联收包网卡才能使用。 2)vpp 的 l2tp 实现是剥除了 ip6 和隧道封装,并留下内部二层头,该模型基于 RFC 4719 中的 PW 终端模型。 vpp 的 l2tp 模块使用 PW termination 实现 LCCE ,而非实现 LAC 或 LNS 功能。如果要基于 vpp 的 l2tp 模块开发 LAC ,此模型只是雏形,还需要增加认证等各方面内容。 ![leec](../../_static/vpp_l2tp_leec.png) #### 2、搭建 L2TP 隧道: 1. 组网拓扑: ![topology](../../_static/vpp_l2tp_topology.png) 2. VPP1 配置: 1)、创建 veth pair 虚拟网络设备,其中 vpp1out 作为 VPP1 的 Host Interface , vpp1host 模拟客户端发起的接口。 ```bash $ ip link add name vpp1out type veth peer name vpp1host $ ip link set dev vpp1out up $ ip link set dev vpp1host up $ ip addr add 10.1.1.1/24 dev vpp1host ``` 2)、配置开启 L2TP plugin ,配置 L2TP 使用 lookup-session-id 作为查询条件。 ```bash $ vi /etc/vpp/startup.conf ... plugins { ... plugin l2tp_plugin.so { enable } plugin l2tp_test_plugin.so { enable } } ... l2tp { lookup-session-id } ``` 3)、在 vppctl 中增加配置,模拟 L2TP 请求服务器: ```bash # 配置物理接口,该接口与对端物理接口同一网段,将收发 L2TP 报文 vpp$ set ip6 address GigabitEthernet0/7/0 fd00::1/8 vpp$ set int state GigabitEthernet0/7/0 up vpp$ set interface ip6 l2tpv3 GigabitEthernet0/7/0 # 创建 l2tpv3 隧道(虚拟接口),该接口将解/封装 l2tp 报文 vpp$ create l2tpv3 tunnel our fd00::1 client fd00::2 local-session-id 10 remote-session-id 20 vpp$ set interface l2 bridge l2tpv3_tunnel0 1 vpp$ set interface state l2tpv3_tunnel0 up # 创建 vpp 虚拟接口与模拟客户端直连 vpp$ create host-interface name vpp1out vpp$ set int state host-vpp1out up vpp$ set int l2 bridge host-vpp1out 1 ``` 3. VPP2 配置: 1)、创建 veth pair 虚拟网络设备,其中 vpp2out 作为 VPP2 的 Host Interface , vpp2host 模拟 server 端的接口。 ```bash $ ip link add name vpp2out type veth peer name vpp2host $ ip link set dev vpp2out up $ ip link set dev vpp2host up $ ip addr add 10.1.1.2/24 dev vpp2host ``` 2)、配置开启 L2TP plugin ,配置 L2TP 使用 lookup-session-id 作为查询条件。 ```bash $ vi /etc/vpp/startup.conf ... plugins { ... plugin l2tp_plugin.so { enable } plugin l2tp_test_plugin.so { enable } } ... l2tp { lookup-session-id } ``` 3)、在 vppctl 中增加配置,模拟 L2TP 接受服务器: ```bash # 配置物理接口,该接口与对端物理接口同一网段,将收发 L2TP 报文 vpp$ set ip6 address GigabitEthernet0/4/0 fd00::2/8 vpp$ set int state GigabitEthernet0/4/0 up vpp$ set interface ip6 l2tpv3 GigabitEthernet0/4/0 # 创建 l2tpv3 隧道(虚拟接口),该接口将解/封装 l2tp 报文 vpp$ create l2tpv3 tunnel our fd00::2 client fd00::1 local-session-id 20 remote-session-id 10 vpp$ set interface l2 bridge l2tpv3_tunnel0 1 vpp$ set interface state l2tpv3_tunnel0 up # 创建 vpp 虚拟接口与模拟服务端直连 vpp$ create host-interface name vpp2out vpp$ set int state host-vpp2out up vpp$ set int l2 bridge host-vpp2out 1 ``` 4. 测试验证: 1)、在 VPP1 客户端 ping VPP2 服务器端的 ip : ```bash $ ping 10.1.1.2 PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data. 64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.241 ms ``` 2)、检查 VPP1 的 L2 MAC 学习表: ```bash vpp$ show ip neighbor Time IP Flags Ethernet Interface 188.1579 fd00::2 D fa:16:3e:61:6b:4e GigabitEthernet0/7/0 ``` 3)、抓物理端口的包: ```bash pcap trace tx rx intfc GigabitEthernet0/7/0 max 10000 file vpp1-phy.pcap pcap trace tx rx status pcap trace off ``` ![pcap1](../../_static/vpp_l2tp_pcap1.png) 4)、抓与模拟客户端直连的网卡上的包: ```bash pcap trace tx rx intfc vpp1out max 10000 file vpp1-out.pcap pcap trace tx rx status pcap trace off ``` ![pcap2](../../_static/vpp_l2tp_pcap2.png) 5)、数据报文跟踪: ```bash vpp$ trace add dpdk-input 10 vpp$ show trace vpp$ clear trace vpp$ trace add af-packet-input 10 vpp$ show trace vpp$ clear trace ``` #### 3、代码分析 初始化函数: ```bash VLIB_INIT_FUNCTION (l2tp_init); l2tp_init |_ lm->lookup_type = L2T_LOOKUP_DST_ADDRESS |_ lm->session_by_src_address = hash_create_mem |_ lm->session_by_dst_address = hash_create_mem |_ lm->session_by_session_id = hash_create |_ ip_get_protocol_info |_ unformat_pg_l2tp_header |_ pg_create_edit_group |_ pg_l2tp_header_init |_ vlib_get_node_by_name |_ pg_get_node |_ l2tp_encap_init ``` ```bash VLIB_WORKER_INIT_FUNCTION (l2tp_worker_init); l2tp_worker_init |_ l2tp_encap_init ``` encap 初始化: ```bash # encap.c l2tp_encap_init |_ vlib_node_get_runtime_data (vm, l2t_encap_node.index); VLIB_REGISTER_NODE (l2t_encap_node) |_ VLIB_NODE_FN (l2t_encap_node) |_ dispatch_pipeline # pipeline.h ``` ![nodes](../../_static/vpp_l2tp_nodes.png) ```bash # 节点 : l2tp-decap # file : /vnet/l2tp/decap.c l2t_decap_node |_ dispatch_pipeline |_ stage0 |_ L2T_LOOKUP_DST_ADDRESS |_ L2T_LOOKUP_SESSION_ID |_ last_stage # 解析 L2TP 包 |_ vlib_buffer_get_current |_ vlib_buffer_advance |_ vnet_update_l2_len |_ vlib_add_trace |_ vlib_buffer_enqueue_to_next ``` ```bash # 节点 : l2tp-decap-local # file : /vnet/l2tp/decap.c l2t_decap_node_fn |_ dispatch_pipeline |_ stage0 |_ stage1 |_ last_stage ``` ```C /* dispatch_pipeline 函数 */ /* file : /vnet/pipeline.h */ #if NSTAGES == 2 // stage 0 #endif #if NSTAGES == 3 // stage 0-1 #endif #if NSTAGES == 4 // stage 0-2 #endif #if NSTAGES == 5 // stage 0-3 #endif #if NSTAGES == 6 // stage 0-4 #endif ``` 创建隧道流程: ```bash create_l2tpv3_tunnel_command_fn |_ create_l2tpv3_ipv6_tunnel (lm, &client_address, &our_address, local_session_id, remote_session_id, local_cookie, remote_cookie, l2_sublayer_present, encap_fib_index, &sw_if_index) |_ switch (lm->lookup_type) # 判断类型(方向) |_ local_cookie |_ remote_cookie |_ local_session_id |_ remote_session_id |_ l2_sublayer_present |_ l2tp_hdr_size |_ encap_fib_index |_ hash_set_mem # setup session id 、 dst_address 、 src_address 等 |_ ip6_register_protocol ``` ## PAP 和 CHAP 的区别 PAP 和 CHAP 的区别在于认证过程, PAP 是简单认证,明文传送,客户端直接发送包含用户名/口令的认证请求,服务器端处理并回应。而 CHAP 是加密认证,先由服务器端给客户端发送一个随机码 challenge ,客户端根据 challenge 对口令进行加密,算法是 md5 。然后把这个结果发送给服务器端,服务器端从数据库中取出口令 password2 ,同样进行加密处理。最后比较加密的结果是否相同。如相同,则认证通过,向客户端发送认可消息。 ## 参考: 1、 l2tp : http://www.023wg.com/message/message/cd_feature_l2tp-message_format.html 2、vpp l2tp : https://wiki.fd.io/view/VPP/How_to_add_a_tunnel_encapsulation https://www.cnblogs.com/OceanF/p/9213034.html https://blog.csdn.net/weixin_41623479/article/details/81482814 3、21版本 vpp : https://github.com/FDio/vpp/tree/stable/2101 4、bridge-domain : https://blog.csdn.net/Illina/article/details/88555425 5、其他厂商实现: 华为: https://support.huawei.com/enterprise/zh/doc/EDOC1100058749/8f18a1d2 H3C: https://www.h3c.com/cn/d_200805/605932_30003_0.htm#download http://kms2.h3c.com/View.aspx?id=33627 yamaha: https://www.yamaha.com/products/en/network/techdocs/vpn/l2tpv3/