Build and Verify a Static L2TPv3 Pseudowire with ip l2tp

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.

Before you start

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.

1. Choose the matching values

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.

ValueSite ASite B
Local address192.0.2.10198.51.100.20
Remote address198.51.100.20192.0.2.10
Tunnel ID30004000
Peer tunnel ID40003000
Session ID10002000
Peer session ID20001000
UDP source/destination5000/60006000/5000

Those addresses are documentation ranges, not usable endpoints. Swap in your real values before running anything.

2. Create the tunnel on both hosts

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.

3. Add the Ethernet session

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.

4. Bring the link up and pick its job

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.

5. Add cookies or sequencing only if the peer needs them

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

6. Remove the test configuration

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.

Done means