L2TP¶
第二層隧道協議 (L2TP) 允許將 L2 幀透過 IP 網路進行隧道傳輸。
本文件涵蓋核心的 L2TP 子系統。它記錄了想要使用 L2TP 子系統的應用程式開發人員的核心 API,並提供了一些關於內部實現的技術細節,這些細節可能對核心開發人員和維護人員有用。
概述¶
核心的 L2TP 子系統實現了 L2TPv2 和 L2TPv3 的資料路徑。 L2TPv2 透過 UDP 傳輸。 L2TPv3 透過 UDP 或直接透過 IP(協議 115)傳輸。
L2TP RFC 定義了兩種基本型別的 L2TP 資料包:控制資料包(“控制平面”)和資料資料包(“資料平面”)。核心只處理資料資料包。更復雜的控制資料包由使用者空間處理。
一個 L2TP 隧道攜帶一個或多個 L2TP 會話。每個隧道都與一個套接字關聯。每個會話都與一個虛擬網路裝置相關聯,例如 pppN, l2tpethN,資料幀透過該裝置傳入/傳出 L2TP。 L2TP 標頭中的欄位標識隧道或會話,以及它是控制資料包還是資料資料包。 當使用 Linux 核心 API 設定隧道和會話時,我們只是設定 L2TP 資料路徑。控制協議的所有方面都由使用者空間處理。
這種職責劃分導致了建立隧道和會話時操作的自然順序。該過程如下所示
建立一個隧道套接字。透過該套接字與對等方交換 L2TP 控制協議訊息,以建立隧道。
在核心中建立一個隧道上下文,使用從對等方透過控制協議訊息獲得的資訊。
透過隧道套接字與對等方交換 L2TP 控制協議訊息,以建立會話。
在核心中建立一個會話上下文,使用從對等方透過控制協議訊息獲得的資訊。
L2TP API¶
本節記錄 L2TP 子系統的每個使用者空間 API。
隧道套接字¶
L2TPv2 總是使用 UDP。 L2TPv3 可以使用 UDP 或 IP 封裝。
要建立 L2TP 使用的隧道套接字,可以使用標準的 POSIX 套接字 API。
例如,對於使用 IPv4 地址和 UDP 封裝的隧道
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
或者對於使用 IPv6 地址和 IP 封裝的隧道
int sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_L2TP);
這裡不需要介紹 UDP 套接字程式設計。
IPPROTO_L2TP 是核心 L2TP 子系統實現的 IP 協議型別。 L2TPIP 套接字地址在 include/uapi/linux/l2tp.h 中的 struct sockaddr_l2tpip 和 struct sockaddr_l2tpip6 中定義。該地址包括 L2TP 隧道(連線)ID。 要使用 L2TP IP 封裝,L2TPv3 應用程式應使用本地分配的隧道 ID 繫結 L2TPIP 套接字。 當對等方的隧道 ID 和 IP 地址已知時,必須執行連線。
如果 L2TP 應用程式需要處理來自使用 L2TPIP 的對等方的 L2TPv3 隧道設定請求,它必須開啟一個專用的 L2TPIP 套接字來偵聽這些請求,並使用隧道 ID 0 繫結套接字,因為隧道設定請求的地址是隧道 ID 0。
當隧道套接字關閉時,L2TP 隧道及其所有會話都會自動關閉。
Netlink API¶
L2TP 應用程式使用 netlink 來管理核心中的 L2TP 隧道和會話例項。 L2TP netlink API 在 include/uapi/linux/l2tp.h 中定義。
L2TP 使用 通用 Netlink (GENL)。 定義了幾個命令:針對隧道和會話例項的建立、刪除、修改和獲取,例如 L2TP_CMD_TUNNEL_CREATE。 API 標頭列出了每個命令可以使用的 netlink 屬性型別。
隧道和會話例項由本地唯一的 32 位 ID 標識。 L2TP 隧道 ID 由 L2TP_ATTR_CONN_ID 和 L2TP_ATTR_PEER_CONN_ID 屬性給出,L2TP 會話 ID 由 L2TP_ATTR_SESSION_ID 和 L2TP_ATTR_PEER_SESSION_ID 屬性給出。 如果使用 netlink 來管理 L2TPv2 隧道和會話例項,則 L2TPv2 16 位隧道/會話 ID 將在此類屬性中強制轉換為 32 位值。
在 L2TP_CMD_TUNNEL_CREATE 命令中,L2TP_ATTR_FD 告訴核心正在使用的隧道套接字 fd。 如果未指定,核心會為隧道建立一個核心套接字,使用在 L2TP_ATTR_IP[6]_SADDR、L2TP_ATTR_IP[6]_DADDR、L2TP_ATTR_UDP_SPORT、L2TP_ATTR_UDP_DPORT 屬性中設定的 IP 引數。 核心套接字用於實現非託管 L2TPv3 隧道(iproute2 的“ip l2tp”命令)。 如果給定了 L2TP_ATTR_FD,則它必須是已繫結和連線的套接字 fd。 本文件後面會提供有關非託管隧道的更多資訊。
L2TP_CMD_TUNNEL_CREATE 屬性:-
屬性 |
必需 |
用途 |
|---|---|---|
CONN_ID |
是 |
設定隧道(連線)ID。 |
PEER_CONN_ID |
是 |
設定對等隧道(連線)ID。 |
PROTO_VERSION |
是 |
協議版本。 2 或 3。 |
ENCAP_TYPE |
是 |
封裝型別:UDP 或 IP。 |
FD |
否 |
隧道套接字檔案描述符。 |
UDP_CSUM |
否 |
啟用 IPv4 UDP 校驗和。 僅當未設定 FD 時使用。 |
UDP_ZERO_CSUM6_TX |
否 |
傳輸時將 IPv6 UDP 校驗和歸零。 僅當未設定 FD 時使用。 |
UDP_ZERO_CSUM6_RX |
否 |
接收時將 IPv6 UDP 校驗和歸零。 僅當未設定 FD 時使用。 |
IP_SADDR |
否 |
IPv4 源地址。 僅當未設定 FD 時使用。 |
IP_DADDR |
否 |
IPv4 目標地址。 僅當未設定 FD 時使用。 |
UDP_SPORT |
否 |
UDP 源埠。 僅當未設定 FD 時使用。 |
UDP_DPORT |
否 |
UDP 目標埠。 僅當未設定 FD 時使用。 |
IP6_SADDR |
否 |
IPv6 源地址。 僅當未設定 FD 時使用。 |
IP6_DADDR |
否 |
IPv6 目標地址。 僅當未設定 FD 時使用。 |
DEBUG |
否 |
除錯標誌。 |
L2TP_CMD_TUNNEL_DESTROY 屬性:-
屬性 |
必需 |
用途 |
|---|---|---|
CONN_ID |
是 |
標識要銷燬的隧道 ID。 |
L2TP_CMD_TUNNEL_MODIFY 屬性:-
屬性 |
必需 |
用途 |
|---|---|---|
CONN_ID |
是 |
標識要修改的隧道 ID。 |
DEBUG |
否 |
除錯標誌。 |
L2TP_CMD_TUNNEL_GET 屬性:-
屬性 |
必需 |
用途 |
|---|---|---|
CONN_ID |
否 |
標識要查詢的隧道 ID。 在 DUMP 請求中忽略。 |
L2TP_CMD_SESSION_CREATE 屬性:-
屬性 |
必需 |
用途 |
|---|---|---|
CONN_ID |
是 |
父隧道 ID。 |
SESSION_ID |
是 |
設定會話 ID。 |
PEER_SESSION_ID |
是 |
設定父會話 ID。 |
PW_TYPE |
是 |
設定偽線型別。 |
DEBUG |
否 |
除錯標誌。 |
RECV_SEQ |
否 |
啟用 rx 資料序列號。 |
SEND_SEQ |
否 |
啟用 tx 資料序列號。 |
LNS_MODE |
否 |
啟用 LNS 模式(自動啟用資料序列號)。 |
RECV_TIMEOUT |
否 |
重新排序接收到的資料包時等待的超時。 |
L2SPEC_TYPE |
否 |
設定第 2 層特定子層型別(僅限 L2TPv3)。 |
COOKIE |
否 |
設定可選 Cookie(僅限 L2TPv3)。 |
PEER_COOKIE |
否 |
設定可選的對等 Cookie(僅限 L2TPv3)。 |
IFNAME |
否 |
設定介面名稱(僅限 L2TPv3)。 |
對於乙太網會話型別,這將建立一個 l2tpeth 虛擬介面,然後可以根據需要對其進行配置。 對於 PPP 會話型別,還必須開啟並連線 PPPoL2TP 套接字,將其對映到新會話。 這將在後面的“PPPoL2TP 套接字”中介紹。
L2TP_CMD_SESSION_DESTROY 屬性:-
屬性 |
必需 |
用途 |
|---|---|---|
CONN_ID |
是 |
標識要銷燬的會話的父隧道 ID。 |
SESSION_ID |
是 |
標識要銷燬的會話 ID。 |
IFNAME |
否 |
按介面名稱標識會話。 如果設定,這將覆蓋任何 CONN_ID 和 SESSION_ID 屬性。 目前僅支援 L2TPv3 乙太網會話。 |
L2TP_CMD_SESSION_MODIFY 屬性:-
屬性 |
必需 |
用途 |
|---|---|---|
CONN_ID |
是 |
標識要修改的會話的父隧道 ID。 |
SESSION_ID |
是 |
標識要修改的會話 ID。 |
IFNAME |
否 |
按介面名稱標識會話。 如果設定,這將覆蓋任何 CONN_ID 和 SESSION_ID 屬性。 目前僅支援 L2TPv3 乙太網會話。 |
DEBUG |
否 |
除錯標誌。 |
RECV_SEQ |
否 |
啟用 rx 資料序列號。 |
SEND_SEQ |
否 |
啟用 tx 資料序列號。 |
LNS_MODE |
否 |
啟用 LNS 模式(自動啟用資料序列號)。 |
RECV_TIMEOUT |
否 |
重新排序接收到的資料包時等待的超時。 |
L2TP_CMD_SESSION_GET 屬性:-
屬性 |
必需 |
用途 |
|---|---|---|
CONN_ID |
否 |
標識要查詢的隧道 ID。 對於 DUMP 請求,此項被忽略。 |
SESSION_ID |
否 |
標識要查詢的會話 ID。 對於 DUMP 請求,此項被忽略。 |
IFNAME |
否 |
按介面名稱標識會話。 如果設定,這將覆蓋任何 CONN_ID 和 SESSION_ID 屬性。 對於 DUMP 請求,此項被忽略。 目前僅支援 L2TPv3 乙太網會話。 |
應用程式開發人員應參閱 include/uapi/linux/l2tp.h 以獲取 netlink 命令和屬性定義。
使用 libmnl 的使用者空間程式碼示例
開啟 L2TP netlink 套接字
struct nl_sock *nl_sock; int l2tp_nl_family_id; nl_sock = nl_socket_alloc(); genl_connect(nl_sock); genl_id = genl_ctrl_resolve(nl_sock, L2TP_GENL_NAME);建立隧道
struct nlmsghdr *nlh; struct genlmsghdr *gnlh; nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = genl_id; /* assigned to genl socket */ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = seq; gnlh = mnl_nlmsg_put_extra_header(nlh, sizeof(*gnlh)); gnlh->cmd = L2TP_CMD_TUNNEL_CREATE; gnlh->version = L2TP_GENL_VERSION; gnlh->reserved = 0; mnl_attr_put_u32(nlh, L2TP_ATTR_FD, tunl_sock_fd); mnl_attr_put_u32(nlh, L2TP_ATTR_CONN_ID, tid); mnl_attr_put_u32(nlh, L2TP_ATTR_PEER_CONN_ID, peer_tid); mnl_attr_put_u8(nlh, L2TP_ATTR_PROTO_VERSION, protocol_version); mnl_attr_put_u16(nlh, L2TP_ATTR_ENCAP_TYPE, encap);建立會話
struct nlmsghdr *nlh; struct genlmsghdr *gnlh; nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = genl_id; /* assigned to genl socket */ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = seq; gnlh = mnl_nlmsg_put_extra_header(nlh, sizeof(*gnlh)); gnlh->cmd = L2TP_CMD_SESSION_CREATE; gnlh->version = L2TP_GENL_VERSION; gnlh->reserved = 0; mnl_attr_put_u32(nlh, L2TP_ATTR_CONN_ID, tid); mnl_attr_put_u32(nlh, L2TP_ATTR_PEER_CONN_ID, peer_tid); mnl_attr_put_u32(nlh, L2TP_ATTR_SESSION_ID, sid); mnl_attr_put_u32(nlh, L2TP_ATTR_PEER_SESSION_ID, peer_sid); mnl_attr_put_u16(nlh, L2TP_ATTR_PW_TYPE, pwtype); /* there are other session options which can be set using netlink * attributes during session creation -- see l2tp.h */刪除會話
struct nlmsghdr *nlh; struct genlmsghdr *gnlh; nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = genl_id; /* assigned to genl socket */ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = seq; gnlh = mnl_nlmsg_put_extra_header(nlh, sizeof(*gnlh)); gnlh->cmd = L2TP_CMD_SESSION_DELETE; gnlh->version = L2TP_GENL_VERSION; gnlh->reserved = 0; mnl_attr_put_u32(nlh, L2TP_ATTR_CONN_ID, tid); mnl_attr_put_u32(nlh, L2TP_ATTR_SESSION_ID, sid);刪除隧道及其所有會話(如果有)
struct nlmsghdr *nlh; struct genlmsghdr *gnlh; nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = genl_id; /* assigned to genl socket */ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = seq; gnlh = mnl_nlmsg_put_extra_header(nlh, sizeof(*gnlh)); gnlh->cmd = L2TP_CMD_TUNNEL_DELETE; gnlh->version = L2TP_GENL_VERSION; gnlh->reserved = 0; mnl_attr_put_u32(nlh, L2TP_ATTR_CONN_ID, tid);
PPPoL2TP 會話套接字 API¶
對於 PPP 會話型別,必須開啟 PPPoL2TP 套接字並將其連線到 L2TP 會話。
建立 PPPoL2TP 套接字時,應用程式在套接字 connect() 呼叫中向核心提供有關隧道和會話的資訊。 提供源隧道 ID 和目標隧道 ID 以及會話 ID,以及 UDP 或 L2TPIP 套接字的檔案描述符。 請參閱 include/linux/if_pppol2tp.h 中的 struct pppol2tp_addr。 由於歷史原因,對於 L2TPv2/L2TPv3 IPv4/IPv6 隧道,地址結構略有不同,並且使用者空間必須使用與隧道套接字型別匹配的適當結構。
使用者空間可以使用 setsockopt 和 ioctl 在 PPPoX 套接字上控制隧道或會話的行為。 支援以下套接字選項:-
DEBUG |
除錯訊息類別的位掩碼。 請參見下文。 |
SENDSEQ |
|
RECVSEQ |
|
LNSMODE |
|
REORDERTO |
重新排序超時(以毫秒為單位)。 如果為 0,則不要嘗試重新排序。 |
除了標準的 PPP ioctl 之外,還提供 PPPIOCGL2TPSTATS,以使用相應隧道或會話的 PPPoX 套接字從核心檢索隧道和會話統計資訊。
使用者空間程式碼示例
建立會話 PPPoX 資料套接字
/* Input: the L2TP tunnel UDP socket `tunnel_fd`, which needs to be * bound already (both sockname and peername), otherwise it will not be * ready. */ struct sockaddr_pppol2tp sax; int session_fd; int ret; session_fd = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP); if (session_fd < 0) return -errno; sax.sa_family = AF_PPPOX; sax.sa_protocol = PX_PROTO_OL2TP; sax.pppol2tp.fd = tunnel_fd; sax.pppol2tp.addr.sin_addr.s_addr = addr->sin_addr.s_addr; sax.pppol2tp.addr.sin_port = addr->sin_port; sax.pppol2tp.addr.sin_family = AF_INET; sax.pppol2tp.s_tunnel = tunnel_id; sax.pppol2tp.s_session = session_id; sax.pppol2tp.d_tunnel = peer_tunnel_id; sax.pppol2tp.d_session = peer_session_id; /* session_fd is the fd of the session's PPPoL2TP socket. * tunnel_fd is the fd of the tunnel UDP / L2TPIP socket. */ ret = connect(session_fd, (struct sockaddr *)&sax, sizeof(sax)); if (ret < 0 ) { close(session_fd); return -errno; } return session_fd;
L2TP 控制資料包仍可在 tunnel_fd 上讀取。
建立 PPP 通道
/* Input: the session PPPoX data socket `session_fd` which was created * as described above. */ int ppp_chan_fd; int chindx; int ret; ret = ioctl(session_fd, PPPIOCGCHAN, &chindx); if (ret < 0) return -errno; ppp_chan_fd = open("/dev/ppp", O_RDWR); if (ppp_chan_fd < 0) return -errno; ret = ioctl(ppp_chan_fd, PPPIOCATTCHAN, &chindx); if (ret < 0) { close(ppp_chan_fd); return -errno; } return ppp_chan_fd;
LCP PPP 幀可在 ppp_chan_fd 上讀取。
建立 PPP 介面
/* Input: the PPP channel `ppp_chan_fd` which was created as described * above. */ int ifunit = -1; int ppp_if_fd; int ret; ppp_if_fd = open("/dev/ppp", O_RDWR); if (ppp_if_fd < 0) return -errno; ret = ioctl(ppp_if_fd, PPPIOCNEWUNIT, &ifunit); if (ret < 0) { close(ppp_if_fd); return -errno; } ret = ioctl(ppp_chan_fd, PPPIOCCONNECT, &ifunit); if (ret < 0) { close(ppp_if_fd); return -errno; } return ppp_if_fd;
IPCP/IPv6CP PPP 幀可在 ppp_if_fd 上讀取。
然後可以使用 netlink 的 RTM_NEWLINK、RTM_NEWADDR、RTM_NEWROUTE 或 ioctl 的 SIOCSIFMTU、SIOCSIFADDR、SIOCSIFDSTADDR、SIOCSIFNETMASK、SIOCSIFFLAGS,或使用 ip 命令,像往常一樣配置 ppp<ifunit> 介面。
透過橋接要橋接的兩個 L2TP 會話的 PPP 通道,支援橋接具有 PPP 偽線型別的 L2TP 會話(這也稱為 L2TP 隧道交換或 L2TP 多跳)
/* Input: the session PPPoX data sockets `session_fd1` and `session_fd2` * which were created as described further above. */ int ppp_chan_fd; int chindx1; int chindx2; int ret; ret = ioctl(session_fd1, PPPIOCGCHAN, &chindx1); if (ret < 0) return -errno; ret = ioctl(session_fd2, PPPIOCGCHAN, &chindx2); if (ret < 0) return -errno; ppp_chan_fd = open("/dev/ppp", O_RDWR); if (ppp_chan_fd < 0) return -errno; ret = ioctl(ppp_chan_fd, PPPIOCATTCHAN, &chindx1); if (ret < 0) { close(ppp_chan_fd); return -errno; } ret = ioctl(ppp_chan_fd, PPPIOCBRIDGECHAN, &chindx2); close(ppp_chan_fd); if (ret < 0) return -errno; return 0;
可以注意到,在橋接 PPP 通道時,PPP 會話不會在本地終止,並且不會建立本地 PPP 介面。 在一個通道上到達的 PPP 幀直接傳遞到另一個通道,反之亦然。
不需要保持 PPP 通道開啟。 只需要保持會話 PPPoX 資料套接字開啟。
更一般地,也可以以相同的方式將 PPPoL2TP PPP 通道與其他型別的 PPP 通道(例如 PPPoE)橋接。
有關 PPP 端的更多詳細資訊,請參見 PPP 通用驅動程式和通道介面。
舊的僅限 L2TPv2 的 API¶
當 L2TP 首次在 2.6.23 版本中新增到 Linux 核心時,它僅實現了 L2TPv2,並且不包含 netlink API。 而是僅使用 PPPoL2TP 套接字直接管理核心中的隧道和會話例項。 PPPoL2TP 套接字的使用方式與“PPPoL2TP 會話套接字 API”部分中描述的方式相同,但隧道和會話例項是在套接字的 connect() 上自動建立的,而不是透過單獨的 netlink 請求建立的
隧道是使用隧道管理套接字管理的,該套接字是專用的 PPPoL2TP 套接字,已連線到(無效的)會話 ID 0。當連線 PPPoL2TP 隧道管理套接字時,會建立 L2TP 隧道例項,並在關閉套接字時銷燬。
當 PPPoL2TP 套接字連線到非零會話 ID 時,將在核心中建立會話例項。 會話引數是使用 setsockopt 設定的。 當關閉套接字時,L2TP 會話例項會被銷燬。
此 API 仍受支援,但不鼓勵使用。 而是,新的 L2TPv2 應用程式應首先使用 netlink 建立隧道和會話,然後為會話建立 PPPoL2TP 套接字。
非託管 L2TPv3 隧道¶
核心 L2TP 子系統還支援靜態(非託管)L2TPv3 隧道。 非託管隧道沒有使用者空間隧道套接字,並且不與對等方交換控制訊息來設定隧道;隧道是在隧道的每一端手動配置的。 所有配置都是使用 netlink 完成的。 在這種情況下,不需要 L2TP 使用者空間應用程式 - 隧道套接字由核心建立,並使用在 L2TP_CMD_TUNNEL_CREATE netlink 請求中傳送的引數進行配置。 iproute2 的 ip 實用程式具有用於管理靜態 L2TPv3 隧道的命令; 執行 ip l2tp help 以獲取更多資訊。
除錯¶
L2TP 子系統透過 debugfs 檔案系統提供了一系列除錯介面。
要訪問這些介面,必須首先掛載 debugfs 檔案系統
# mount -t debugfs debugfs /debug
然後可以訪問 l2tp 目錄下的檔案,從而提供核心中存在的當前隧道和會話上下文的摘要
# cat /debug/l2tp/tunnels
應用程式不應使用 debugfs 檔案來獲取 L2TP 狀態資訊,因為檔案格式可能會更改。 它的實現目的是提供額外的除錯資訊來幫助診斷問題。 應用程式應改為使用 netlink API。
此外,L2TP 子系統使用標準核心事件跟蹤 API 實現跟蹤點。 可用的 L2TP 事件可以按如下方式檢視
# find /debug/tracing/events/l2tp
最後,為了向後相容原始 pppol2tp 程式碼,還提供了 /proc/net/pppol2tp。 它僅列出有關 L2TPv2 隧道和會話的資訊。 不鼓勵使用它。
內部實現¶
本節適用於核心開發人員和維護人員。
套接字¶
UDP 套接字由網路核心實現。 當使用 UDP 套接字建立 L2TP 隧道時,透過在 UDP 套接字上設定 encap_rcv 和 encap_destroy 回撥,將套接字設定為封裝的 UDP 套接字。 當在套接字上收到資料包時,將呼叫 l2tp_udp_encap_recv。 當用戶空間關閉套接字時,將呼叫 l2tp_udp_encap_destroy。
L2TPIP 套接字在 net/l2tp/l2tp_ip.c 和 net/l2tp/l2tp_ip6.c 中實現。
隧道¶
核心為每個 L2TP 隧道保留一個 struct l2tp_tunnel 上下文。 l2tp_tunnel 始終與 UDP 或 L2TP/IP 套接字相關聯,並保留隧道中會話的列表。 當隧道首次在 L2TP 核心中註冊時,套接字上的引用計數會增加。 這確保了在 L2TP 的資料結構引用套接字時無法刪除套接字。
隧道由唯一的隧道 ID 標識。 對於 L2TPv2,ID 為 16 位,對於 L2TPv3,ID 為 32 位。 在內部,ID 儲存為 32 位值。
隧道儲存在按隧道 ID 索引的每個網路列表中。 L2TPv2 和 L2TPv3 共享隧道 ID 名稱空間。
處理隧道套接字關閉可能是 L2TP 實現中最棘手的部分。 如果使用者空間關閉隧道套接字,則必須關閉並銷燬 L2TP 隧道及其所有會話。 由於隧道上下文儲存對隧道套接字的引用,因此在隧道 sock_put 其套接字之前,不會呼叫套接字的 sk_destruct。 對於 UDP 套接字,當用戶空間關閉隧道套接字時,將呼叫套接字的 encap_destroy 處理程式,L2TP 使用該處理程式啟動其隧道關閉操作。 對於 L2TPIP 套接字,套接字的關閉處理程式會啟動相同的隧道關閉操作。 首先關閉所有會話。 每個會話都會刪除其隧道引用。 當隧道引用達到零時,隧道會刪除其套接字引用。
會話¶
核心為每個會話保留一個 struct l2tp_session 上下文。 每個會話都有專用資料,這些資料用於特定於會話型別的資料。 對於 L2TPv2,會話始終攜帶 PPP 流量。 對於 L2TPv3,會話可以攜帶乙太網幀(乙太網偽線)或其他資料型別,例如 PPP、ATM、HDLC 或幀中繼。 Linux 目前僅實現乙太網和 PPP 會話型別。
某些 L2TP 會話型別也具有套接字(PPP 偽線),而其他型別沒有套接字(乙太網偽線)。
與隧道一樣,L2TP 會話由唯一的會話 ID 標識。 與隧道 ID 一樣,對於 L2TPv2,會話 ID 為 16 位,對於 L2TPv3,會話 ID 為 32 位。 在內部,ID 儲存為 32 位值。
會話保留對其父隧道的引用,以確保在有一個或多個會話引用隧道時隧道保持存在。
會話儲存在每個網路列表中。 L2TPv2 會話和 L2TPv3 會話儲存在單獨的列表中。 L2TPv2 會話的鍵由 32 位金鑰組成,該金鑰由 16 位隧道 ID 和 16 位會話 ID 組成。 L2TPv3 會話的鍵由 32 位會話 ID 組成,因為 L2TPv3 會話 ID 在所有隧道中都是唯一的。
儘管 L2TPv3 RFC 規定 L2TPv3 會話 ID 不受隧道限制,但 Linux 實現歷來允許這樣做。 使用按 sk 和會話 ID 鍵控的每個網路雜湊表來支援此類會話 ID 衝突。 查詢 L2TPv3 會話時,列表條目可能會連結到具有該會話 ID 的多個會話,在這種情況下,將使用與給定 sk(隧道)匹配的會話。
PPP¶
net/l2tp/l2tp_ppp.c 實現了 PPPoL2TP 套接字系列。 每個 PPP 會話都有一個 PPPoL2TP 套接字。
PPPoL2TP 套接字的 sk_user_data 引用 l2tp_session。
使用者空間使用 PPPoL2TP 套接字透過 L2TP 傳送和接收 PPP 資料包。 只有 PPP 控制幀透過此套接字傳遞:PPP 資料資料包完全由核心處理,並在 L2TP 會話及其關聯的 pppN 網路裝置之間透過核心 PPP 子系統的 PPP 通道介面傳遞。
L2TP PPP 實現透過關閉其相應的 L2TP 會話來處理 PPPoL2TP 套接字的關閉。 這很複雜,因為它必須考慮與 netlink 會話建立/銷燬請求和嘗試重新連線到正在關閉的會話的 pppol2tp_connect 競爭。 PPP 會話保留對其關聯套接字的引用,以便在會話引用它時套接字保持存在。
乙太網¶
net/l2tp/l2tp_eth.c 實現了 L2TPv3 乙太網偽線。 它為每個會話管理一個網路裝置。
L2TP 乙太網會話透過 netlink 請求建立和銷燬,或者在隧道被銷燬時銷燬。與 PPP 會話不同,乙太網會話沒有關聯的套接字。
其他¶
RFC¶
核心程式碼實現了以下 RFC 中指定的資料路徑功能
RFC2661 |
L2TPv2 |
|
RFC3931 |
L2TPv3 |
|
RFC4719 |
L2TPv3 乙太網 |
實現¶
許多開源應用程式使用 L2TP 核心子系統
iproute2 |
|
go-l2tp |
|
tunneldigger |
|
xl2tpd |
侷限性¶
當前的實現存在一些侷限性
與 openvswitch 的介面尚未實現。將 OVS 乙太網和 VLAN 埠對映到 L2TPv3 隧道可能很有用。
VLAN 偽線使用配置了 VLAN 子介面的
l2tpethN介面實現。由於 L2TPv3 VLAN 偽線僅攜帶一個 VLAN,因此最好使用單個 netdevice,而不是每個 VLAN 會話使用一個l2tpethN和l2tpethN:M 對。已為此添加了 netlink 屬性L2TP_ATTR_VLAN_ID,但從未實現。
測試¶
核心內建的自檢測試對非託管 L2TPv3 乙太網功能進行測試。請參閱 tools/testing/selftests/net/l2tp.sh。
另一個測試套件 l2tp-ktest 涵蓋了所有 L2TP API 和隧道/會話型別。將來可能會將其整合到核心的內建 L2TP 自檢測試中。