Static L2TPv3 has no control daemon, so ip l2tp only does exactly what you tell it, on both ends, with nothing to negotiate a mismatch away. Get one number wrong on one side and the tunnel just sits there silently doing nothing. By the end of this guide two Linux hosts will have a matched, unmanaged L2TPv3 Ethernet session, each exposing an l2tpeth interface you can use as a point-to-point IP link or add to a bridge.
This uses the iproute2 6.1.0 command installed on this machine, and takes about 15 minutes once you have addresses and an agreed ID plan.
ip from iproute2.Checkpoint: creating tunnels, creating links, changing link state, assigning addresses and changing bridge membership all need root. Use sudo for each state-changing command below. The inspection commands do not need it.
The IDs are deliberately reversed at the peer: one site's tunnel_id must equal the other site's peer_tunnel_id, and the same rule applies to session_id/peer_session_id and to the UDP source and destination ports.
| Value | Site A | Site B |
|---|---|---|
| Local address | 192.0.2.10 | 198.51.100.20 |
| Remote address | 198.51.100.20 | 192.0.2.10 |
| Tunnel ID | 3000 | 4000 |
| Peer tunnel ID | 4000 | 3000 |
| Session ID | 1000 | 2000 |
| Peer session ID | 2000 | 1000 |
| UDP source/destination | 5000/6000 | 6000/5000 |
Those addresses are documentation ranges, not usable endpoints. Swap in your real values before running anything.
On site A:
$ sudo ip l2tp add tunnel remote 198.51.100.20 local 192.0.2.10 \
tunnel_id 3000 peer_tunnel_id 4000 encap udp \
udp_sport 5000 udp_dport 6000
On site B, everything reverses: endpoint, IDs, ports:
$ sudo ip l2tp add tunnel remote 192.0.2.10 local 198.51.100.20 \
tunnel_id 4000 peer_tunnel_id 3000 encap udp \
udp_sport 6000 udp_dport 5000
UDP is the usual choice because it plays nicely with ordinary firewall rules. Two checksum defaults worth knowing: IPv4 UDP checksums default to off in this command, while IPv6 UDP transmit and receive checksums default to on. Set udp_csum, udp6_csum_tx or udp6_csum_rx explicitly if your peer needs something different.
Checkpoint: confirm the local tunnel before moving on:
$ ip l2tp show tunnel
Tunnel 3000, encap UDP
From 192.0.2.10 to 198.51.100.20
UDP source / dest ports: 5000 / 6000
Formatting varies between iproute2 builds. Check the endpoint, encapsulation, ports and IDs, not the literal layout.
A tunnel can carry more than one session, but every session needs an existing tunnel to sit in. Add one on each host:
$ sudo ip l2tp add session name l2tpeth0 \
tunnel_id 3000 session_id 1000 peer_session_id 2000
$ sudo ip l2tp add session name l2tpeth0 \
tunnel_id 4000 session_id 2000 peer_session_id 1000
Naming it explicitly keeps later commands unambiguous; leave the name out and iproute2 picks something like l2tpeth0 for you. A duplicate name or an ID already in use is an error, not a silent replacement.
Verify the session and the interface it created:
$ ip l2tp show session
Session 1000 in tunnel 3000
peer_session_id 2000, interface l2tpeth0
$ ip link show dev l2tpeth0
Display format is version-dependent; what matters is that the session exists and the named interface was created.
For a simple IP link, set the MTU, bring it up, and address both ends:
# site A
$ sudo ip link set dev l2tpeth0 mtu 1488 up
$ sudo ip addr add 10.42.1.1 peer 10.42.1.2 dev l2tpeth0
# site B
$ sudo ip link set dev l2tpeth0 mtu 1488 up
$ sudo ip addr add 10.42.1.2 peer 10.42.1.1 dev l2tpeth0
Checkpoint: test the data path from site A:
$ ping -c 3 10.42.1.2
A failure here does not point at one cause. It can be the underlay firewall blocking UDP, mismatched peer values, or the other end never being configured, this is an unmanaged tunnel, so a successful local create tells you nothing about whether the peer exists.
For non-IP Ethernet, do not address l2tpeth0 directly. Bridge it with a physical interface on each endpoint instead:
$ sudo ip link set dev l2tpeth0 mtu 1446 up
$ sudo ip link add name br-l2tp type bridge
$ sudo ip link set dev l2tpeth0 master br-l2tp
$ sudo ip link set dev eth0 master br-l2tp
$ sudo ip link set dev br-l2tp up
Warning: swap in your real LAN interface for eth0. Adding a live interface to a bridge changes forwarding immediately and can knock out management access, so do this from a console or in a maintenance window with an out-of-band rollback ready. If large bridged frames carry protocols that cannot tolerate fragmentation, look at the relevant IPv4 or IPv6 netfilter defragmentation module and test the actual workload.
The basic session has no cookie and no sequence processing. A cookie is an 8 or 16 hex digit value, and it has to match the peer's expected value in the opposite direction, for example:
$ sudo ip l2tp add session tunnel_id 3000 session_id 1000 \
peer_session_id 2000 cookie 014d3636 peer_cookie 9abc1234
seq can be none, send, recv or both and must agree with the peer's Layer 2 header expectations.l2spec_type defaults differ. Linux defaults to Default Layer2SpecificHeader; some vendor equipment defaults to None. The manpage documents none or default, check the peer before changing it.Warning: deletion stops the virtual link and removes its session interface. Make sure nothing production is riding on it first.
$ sudo ip l2tp del session tunnel_id 3000 session_id 1000
$ sudo ip l2tp del tunnel tunnel_id 3000
$ ip l2tp show tunnel
Repeat with tunnel 4000 and session 2000 on site B. Delete sessions before their parent tunnels, deleting a tunnel with sessions still attached fails outright. If you assigned addresses or touched a bridge, undo those separately with ip addr del and ip link set dev l2tpeth0 nomaster, and do not remove a shared bridge just to tidy up this example.
ip l2tp show tunnel and ip l2tp show session show what you expect.l2tpeth interface is up with an MTU suited to the encapsulation.