1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
|
From: Christian Breunig <christian@breunig.cc>
Date: Mon, 14 May 2026 13:11:00 +0200
Subject: [PATCH] net/l2tp: allow unmanaged tunnel setup without route to peer
Kernel-created L2TPv3 tunnels (genetlink L2TP_CMD_TUNNEL_CREATE without
L2TP_ATTR_FD) used udp_sock_create() and kernel_connect(), which invoke
__ip4_datagram_connect() / __ip6_datagram_connect(). Those paths insist on
a successful FIB lookup at connect time. If no route to the configured
remote existed yet, tunnel and interface creation failed.
The data path already resolves routes on transmit (e.g. __ip_queue_xmit(),
inet6_csk_route_socket()). This change defers requiring a route until
packets are sent.
Details:
- UDP encapsulation: bind with udp_sock_create() after zeroing peer_udp_port,
then l2tp_udp_sk_set_peer() sets daddr/dport and socket "connected" state
without caching sk_dst from connect.
- IPv4 L2TP/IP (l2tp_ip): on -ENETUNREACH / -EHOSTUNREACH from
__ip4_datagram_connect(), l2tp_ip_connect_deferred() installs peer and
bind-table updates without a connect-time route.
- IPv6 L2TP/IP (l2tp_ip6): same for __ip6_datagram_connect(), including
IPv4-mapped peers and scope / bound-device checks aligned with the normal
connect path.
Forwarding still only happens once the FIB can reach the peer. Until then
outgoing packets follow the existing no-route drop path.
Assisted-by: Cursor:claude-4.8-opus
Signed-off-by: Christian Breunig <christian@breunig.cc>
---
net/l2tp/l2tp_core.c | 65 +++++++++++++++++++++++-
net/l2tp/l2tp_ip.c | 37 ++++++++++++++
net/l2tp/l2tp_ip6.c | 117 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 218 insertions(+), 1 deletion(-)
diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c
index fd78928e9be2..0be01b28b7a8 100644
--- a/net/l2tp/l2tp_core.c
+++ b/net/l2tp/l2tp_core.c
@@ -55,6 +55,8 @@
#include <net/inet_ecn.h>
#include <net/ip6_route.h>
#include <net/ip6_checksum.h>
+#include <net/sock_reuseport.h>
+#include <net/transp_v6.h>
#include <asm/byteorder.h>
#include <linux/atomic.h>
@@ -1454,6 +1456,67 @@ static void l2tp_tunnel_del_work(struct work_struct *work)
* These sockets are freed when the namespace exits using the pernet
* exit hook.
*/
+
+static int l2tp_udp_sk_set_peer(struct socket *sock, struct udp_port_cfg *cfg)
+{
+ struct sock *sk = sock->sk;
+ struct inet_sock *inet;
+
+ lock_sock(sk);
+ inet = inet_sk(sk);
+ sk_dst_reset(sk);
+
+#if IS_ENABLED(CONFIG_IPV6)
+ if (cfg->family == AF_INET6) {
+ struct ipv6_pinfo *np = inet6_sk(sk);
+
+ sk->sk_v6_daddr = cfg->peer_ip6;
+ np->flow_label = 0;
+ inet->inet_dport = cfg->peer_udp_port;
+ } else
+#endif
+ {
+ inet->inet_daddr = cfg->peer_ip.s_addr;
+ inet->inet_dport = cfg->peer_udp_port;
+ }
+
+ reuseport_has_conns_set(sk);
+ sk->sk_state = TCP_ESTABLISHED;
+ sk_set_txhash(sk);
+ atomic_set(&inet->inet_id, get_random_u16());
+ release_sock(sk);
+ return 0;
+}
+
+static int l2tp_tunnel_udp_sock_create(struct net *net, struct udp_port_cfg *cfg,
+ struct socket **sockp)
+{
+ struct udp_port_cfg cfg_bind = *cfg;
+ int err;
+ struct socket *sock;
+
+ cfg_bind.peer_udp_port = 0;
+
+ err = udp_sock_create(net, &cfg_bind, &sock);
+ if (err < 0)
+ return err;
+
+ if (!cfg->peer_udp_port) {
+ *sockp = sock;
+ return 0;
+ }
+
+ err = l2tp_udp_sk_set_peer(sock, cfg);
+ if (err < 0) {
+ kernel_sock_shutdown(sock, SHUT_RDWR);
+ sock_release(sock);
+ return err;
+ }
+
+ *sockp = sock;
+ return 0;
+}
+
static int l2tp_tunnel_sock_create(struct net *net,
u32 tunnel_id,
u32 peer_tunnel_id,
@@ -1491,7 +1554,7 @@ static int l2tp_tunnel_sock_create(struct net *net,
udp_conf.local_udp_port = htons(cfg->local_udp_port);
udp_conf.peer_udp_port = htons(cfg->peer_udp_port);
- err = udp_sock_create(net, &udp_conf, &sock);
+ err = l2tp_tunnel_udp_sock_create(net, &udp_conf, &sock);
if (err < 0)
goto out;
diff --git a/net/l2tp/l2tp_ip.c b/net/l2tp/l2tp_ip.c
index 29795d2839e8..0d904a50b9a3 100644
--- a/net/l2tp/l2tp_ip.c
+++ b/net/l2tp/l2tp_ip.c
@@ -24,6 +24,7 @@
#include <net/xfrm.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>
+#include <net/sock_reuseport.h>
#include "l2tp_core.h"
@@ -328,6 +329,37 @@ static int l2tp_ip_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
return ret;
}
+/* Install peer address without capturing a route. Outgoing packets resolve
+ * the path in __ip_queue_xmit(); this matches on-demand forwarding once the
+ * FIB can reach the remote.
+ */
+static void __l2tp_ip4_sk_set_peer(struct sock *sk, __be32 daddr, __be16 dport)
+{
+ struct inet_sock *inet = inet_sk(sk);
+
+ sk_dst_reset(sk);
+ inet->inet_daddr = daddr;
+ inet->inet_dport = dport;
+ reuseport_has_conns_set(sk);
+ sk->sk_state = TCP_ESTABLISHED;
+ sk_set_txhash(sk);
+ atomic_set(&inet->inet_id, get_random_u16());
+}
+
+static int l2tp_ip_connect_deferred(struct sock *sk, struct sockaddr_l2tpip *lsa)
+{
+ struct l2tp_ip_net *pn = l2tp_ip_pernet(sock_net(sk));
+
+ __l2tp_ip4_sk_set_peer(sk, lsa->l2tp_addr.s_addr, lsa->l2tp_unused);
+ l2tp_ip_sk(sk)->peer_conn_id = lsa->l2tp_conn_id;
+
+ write_lock_bh(&pn->l2tp_ip_lock);
+ hlist_del_init(&sk->sk_bind_node);
+ sk_add_bind_node(sk, &pn->l2tp_ip_bind_table);
+ write_unlock_bh(&pn->l2tp_ip_lock);
+ return 0;
+}
+
static int l2tp_ip_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
struct sockaddr_l2tpip *lsa = (struct sockaddr_l2tpip *)uaddr;
@@ -349,6 +381,11 @@ static int l2tp_ip_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len
}
rc = __ip4_datagram_connect(sk, uaddr, addr_len);
+ if (rc == -ENETUNREACH || rc == -EHOSTUNREACH) {
+ rc = l2tp_ip_connect_deferred(sk, lsa);
+ goto out_sk;
+ }
+
if (rc < 0)
goto out_sk;
diff --git a/net/l2tp/l2tp_ip6.c b/net/l2tp/l2tp_ip6.c
index ea232f338dcb..e4e34db4c37f 100644
--- a/net/l2tp/l2tp_ip6.c
+++ b/net/l2tp/l2tp_ip6.c
@@ -28,6 +28,8 @@
#include <net/transp_v6.h>
#include <net/addrconf.h>
#include <net/ip6_route.h>
+#include <net/l3mdev.h>
+#include <net/sock_reuseport.h>
#include "l2tp_core.h"
@@ -383,6 +385,116 @@ static int l2tp_ip6_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
return err;
}
+static bool l2tp_ipv6_mapped_addr_any(const struct in6_addr *a)
+{
+ return ipv6_addr_v4mapped(a) && a->s6_addr32[3] == 0;
+}
+
+/* Peer address without a cached dst; outbound path resolves the route. */
+static void __l2tp_ip6_sk_set_peer(struct sock *sk, const struct in6_addr *daddr,
+ __be16 dport, __be32 flowlabel)
+{
+ struct inet_sock *inet = inet_sk(sk);
+ struct ipv6_pinfo *np = inet6_sk(sk);
+
+ sk_dst_reset(sk);
+ sk->sk_v6_daddr = *daddr;
+ np->flow_label = flowlabel;
+ inet->inet_dport = dport;
+ reuseport_has_conns_set(sk);
+ sk->sk_state = TCP_ESTABLISHED;
+ sk_set_txhash(sk);
+}
+
+static int __l2tp_ip6_connect_deferred_mapped(struct sock *sk,
+ struct sockaddr_l2tpip6 *lsa,
+ struct sockaddr_in6 *usin)
+{
+ struct inet_sock *inet = inet_sk(sk);
+ struct ipv6_pinfo *np = inet6_sk(sk);
+ struct sockaddr_in sin;
+ struct l2tp_ip6_net *pn;
+ int err;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = usin->sin6_addr.s6_addr32[3];
+ sin.sin_port = usin->sin6_port;
+
+ err = __ip4_datagram_connect(sk, (struct sockaddr *)&sin, sizeof(sin));
+ if (err == -ENETUNREACH || err == -EHOSTUNREACH) {
+ sk_dst_reset(sk);
+ inet->inet_daddr = sin.sin_addr.s_addr;
+ inet->inet_dport = sin.sin_port;
+ reuseport_has_conns_set(sk);
+ sk->sk_state = TCP_ESTABLISHED;
+ sk_set_txhash(sk);
+ atomic_set(&inet->inet_id, get_random_u16());
+ } else if (err) {
+ return err;
+ }
+
+ ipv6_addr_set_v4mapped(inet->inet_daddr, &sk->sk_v6_daddr);
+ if (ipv6_addr_any(&np->saddr) || l2tp_ipv6_mapped_addr_any(&np->saddr))
+ ipv6_addr_set_v4mapped(inet->inet_saddr, &np->saddr);
+ if (ipv6_addr_any(&sk->sk_v6_rcv_saddr) ||
+ l2tp_ipv6_mapped_addr_any(&sk->sk_v6_rcv_saddr)) {
+ ipv6_addr_set_v4mapped(inet->inet_rcv_saddr, &sk->sk_v6_rcv_saddr);
+ if (sk->sk_prot->rehash)
+ sk->sk_prot->rehash(sk);
+ }
+
+ l2tp_ip6_sk(sk)->peer_conn_id = lsa->l2tp_conn_id;
+ pn = l2tp_ip6_pernet(sock_net(sk));
+ write_lock_bh(&pn->l2tp_ip6_lock);
+ hlist_del_init(&sk->sk_bind_node);
+ sk_add_bind_node(sk, &pn->l2tp_ip6_bind_table);
+ write_unlock_bh(&pn->l2tp_ip6_lock);
+ return 0;
+}
+
+static int l2tp_ip6_connect_deferred(struct sock *sk, struct sockaddr_l2tpip6 *lsa,
+ struct sockaddr_in6 *usin,
+ int addr_len)
+{
+ struct ipv6_pinfo *np = inet6_sk(sk);
+ struct l2tp_ip6_net *pn;
+ int addr_type = ipv6_addr_type(&usin->sin6_addr);
+ const struct in6_addr *daddr = &usin->sin6_addr;
+ __be32 fl6_flowlabel = 0;
+
+ if (addr_type & IPV6_ADDR_MAPPED)
+ return __l2tp_ip6_connect_deferred_mapped(sk, lsa, usin);
+
+ if (inet6_test_bit(SNDFLOW, sk))
+ fl6_flowlabel = usin->sin6_flowinfo & IPV6_FLOWINFO_MASK;
+
+ if (__ipv6_addr_needs_scope_id(addr_type)) {
+ if (addr_len >= sizeof(struct sockaddr_in6) &&
+ usin->sin6_scope_id) {
+ if (!sk_dev_equal_l3scope(sk, usin->sin6_scope_id))
+ return -EINVAL;
+ WRITE_ONCE(sk->sk_bound_dev_if, usin->sin6_scope_id);
+ }
+
+ if (!sk->sk_bound_dev_if && (addr_type & IPV6_ADDR_MULTICAST))
+ WRITE_ONCE(sk->sk_bound_dev_if, READ_ONCE(np->mcast_oif));
+
+ if (!sk->sk_bound_dev_if)
+ return -EINVAL;
+ }
+
+ __l2tp_ip6_sk_set_peer(sk, daddr, usin->sin6_port, fl6_flowlabel);
+ l2tp_ip6_sk(sk)->peer_conn_id = lsa->l2tp_conn_id;
+
+ pn = l2tp_ip6_pernet(sock_net(sk));
+ write_lock_bh(&pn->l2tp_ip6_lock);
+ hlist_del_init(&sk->sk_bind_node);
+ sk_add_bind_node(sk, &pn->l2tp_ip6_bind_table);
+ write_unlock_bh(&pn->l2tp_ip6_lock);
+ return 0;
+}
+
static int l2tp_ip6_connect(struct sock *sk, struct sockaddr *uaddr,
int addr_len)
{
@@ -418,6 +530,11 @@ static int l2tp_ip6_connect(struct sock *sk, struct sockaddr *uaddr,
}
rc = __ip6_datagram_connect(sk, uaddr, addr_len);
+ if (rc == -ENETUNREACH || rc == -EHOSTUNREACH) {
+ rc = l2tp_ip6_connect_deferred(sk, lsa, usin, addr_len);
+ goto out_sk;
+ }
+
if (rc < 0)
goto out_sk;
--
2.39.5
|