1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* L2TP subsystem debugfs |
3 | * |
4 | * Copyright (c) 2010 Katalix Systems Ltd |
5 | */ |
6 | |
7 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/skbuff.h> |
11 | #include <linux/socket.h> |
12 | #include <linux/hash.h> |
13 | #include <linux/l2tp.h> |
14 | #include <linux/in.h> |
15 | #include <linux/etherdevice.h> |
16 | #include <linux/spinlock.h> |
17 | #include <linux/debugfs.h> |
18 | #include <net/sock.h> |
19 | #include <net/ip.h> |
20 | #include <net/icmp.h> |
21 | #include <net/udp.h> |
22 | #include <net/inet_common.h> |
23 | #include <net/inet_hashtables.h> |
24 | #include <net/tcp_states.h> |
25 | #include <net/protocol.h> |
26 | #include <net/xfrm.h> |
27 | #include <net/net_namespace.h> |
28 | #include <net/netns/generic.h> |
29 | |
30 | #include "l2tp_core.h" |
31 | |
32 | static struct dentry *rootdir; |
33 | |
34 | struct l2tp_dfs_seq_data { |
35 | struct net *net; |
36 | netns_tracker ns_tracker; |
37 | int tunnel_idx; /* current tunnel */ |
38 | int session_idx; /* index of session within current tunnel */ |
39 | struct l2tp_tunnel *tunnel; |
40 | struct l2tp_session *session; /* NULL means get next tunnel */ |
41 | }; |
42 | |
43 | static void l2tp_dfs_next_tunnel(struct l2tp_dfs_seq_data *pd) |
44 | { |
45 | /* Drop reference taken during previous invocation */ |
46 | if (pd->tunnel) |
47 | l2tp_tunnel_dec_refcount(tunnel: pd->tunnel); |
48 | |
49 | pd->tunnel = l2tp_tunnel_get_nth(net: pd->net, nth: pd->tunnel_idx); |
50 | pd->tunnel_idx++; |
51 | } |
52 | |
53 | static void l2tp_dfs_next_session(struct l2tp_dfs_seq_data *pd) |
54 | { |
55 | /* Drop reference taken during previous invocation */ |
56 | if (pd->session) |
57 | l2tp_session_dec_refcount(session: pd->session); |
58 | |
59 | pd->session = l2tp_session_get_nth(tunnel: pd->tunnel, nth: pd->session_idx); |
60 | pd->session_idx++; |
61 | |
62 | if (!pd->session) { |
63 | pd->session_idx = 0; |
64 | l2tp_dfs_next_tunnel(pd); |
65 | } |
66 | } |
67 | |
68 | static void *l2tp_dfs_seq_start(struct seq_file *m, loff_t *offs) |
69 | { |
70 | struct l2tp_dfs_seq_data *pd = SEQ_START_TOKEN; |
71 | loff_t pos = *offs; |
72 | |
73 | if (!pos) |
74 | goto out; |
75 | |
76 | if (WARN_ON(!m->private)) { |
77 | pd = NULL; |
78 | goto out; |
79 | } |
80 | pd = m->private; |
81 | |
82 | if (!pd->tunnel) |
83 | l2tp_dfs_next_tunnel(pd); |
84 | else |
85 | l2tp_dfs_next_session(pd); |
86 | |
87 | /* NULL tunnel and session indicates end of list */ |
88 | if (!pd->tunnel && !pd->session) |
89 | pd = NULL; |
90 | |
91 | out: |
92 | return pd; |
93 | } |
94 | |
95 | static void *l2tp_dfs_seq_next(struct seq_file *m, void *v, loff_t *pos) |
96 | { |
97 | (*pos)++; |
98 | return NULL; |
99 | } |
100 | |
101 | static void l2tp_dfs_seq_stop(struct seq_file *p, void *v) |
102 | { |
103 | struct l2tp_dfs_seq_data *pd = v; |
104 | |
105 | if (!pd || pd == SEQ_START_TOKEN) |
106 | return; |
107 | |
108 | /* Drop reference taken by last invocation of l2tp_dfs_next_session() |
109 | * or l2tp_dfs_next_tunnel(). |
110 | */ |
111 | if (pd->session) { |
112 | l2tp_session_dec_refcount(session: pd->session); |
113 | pd->session = NULL; |
114 | } |
115 | if (pd->tunnel) { |
116 | l2tp_tunnel_dec_refcount(tunnel: pd->tunnel); |
117 | pd->tunnel = NULL; |
118 | } |
119 | } |
120 | |
121 | static void l2tp_dfs_seq_tunnel_show(struct seq_file *m, void *v) |
122 | { |
123 | struct l2tp_tunnel *tunnel = v; |
124 | struct l2tp_session *session; |
125 | int session_count = 0; |
126 | int hash; |
127 | |
128 | rcu_read_lock_bh(); |
129 | for (hash = 0; hash < L2TP_HASH_SIZE; hash++) { |
130 | hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) { |
131 | /* Session ID of zero is a dummy/reserved value used by pppol2tp */ |
132 | if (session->session_id == 0) |
133 | continue; |
134 | |
135 | session_count++; |
136 | } |
137 | } |
138 | rcu_read_unlock_bh(); |
139 | |
140 | seq_printf(m, fmt: "\nTUNNEL %u peer %u" , tunnel->tunnel_id, tunnel->peer_tunnel_id); |
141 | if (tunnel->sock) { |
142 | struct inet_sock *inet = inet_sk(tunnel->sock); |
143 | |
144 | #if IS_ENABLED(CONFIG_IPV6) |
145 | if (tunnel->sock->sk_family == AF_INET6) { |
146 | const struct ipv6_pinfo *np = inet6_sk(sk: tunnel->sock); |
147 | |
148 | seq_printf(m, fmt: " from %pI6c to %pI6c\n" , |
149 | &np->saddr, &tunnel->sock->sk_v6_daddr); |
150 | } |
151 | #endif |
152 | if (tunnel->sock->sk_family == AF_INET) |
153 | seq_printf(m, fmt: " from %pI4 to %pI4\n" , |
154 | &inet->inet_saddr, &inet->inet_daddr); |
155 | |
156 | if (tunnel->encap == L2TP_ENCAPTYPE_UDP) |
157 | seq_printf(m, fmt: " source port %hu, dest port %hu\n" , |
158 | ntohs(inet->inet_sport), ntohs(inet->inet_dport)); |
159 | } |
160 | seq_printf(m, fmt: " L2TPv%d, %s\n" , tunnel->version, |
161 | tunnel->encap == L2TP_ENCAPTYPE_UDP ? "UDP" : |
162 | tunnel->encap == L2TP_ENCAPTYPE_IP ? "IP" : |
163 | "" ); |
164 | seq_printf(m, fmt: " %d sessions, refcnt %d/%d\n" , session_count, |
165 | tunnel->sock ? refcount_read(r: &tunnel->sock->sk_refcnt) : 0, |
166 | refcount_read(r: &tunnel->ref_count)); |
167 | seq_printf(m, fmt: " %08x rx %ld/%ld/%ld rx %ld/%ld/%ld\n" , |
168 | 0, |
169 | atomic_long_read(v: &tunnel->stats.tx_packets), |
170 | atomic_long_read(v: &tunnel->stats.tx_bytes), |
171 | atomic_long_read(v: &tunnel->stats.tx_errors), |
172 | atomic_long_read(v: &tunnel->stats.rx_packets), |
173 | atomic_long_read(v: &tunnel->stats.rx_bytes), |
174 | atomic_long_read(v: &tunnel->stats.rx_errors)); |
175 | } |
176 | |
177 | static void l2tp_dfs_seq_session_show(struct seq_file *m, void *v) |
178 | { |
179 | struct l2tp_session *session = v; |
180 | |
181 | seq_printf(m, fmt: " SESSION %u, peer %u, %s\n" , session->session_id, |
182 | session->peer_session_id, |
183 | session->pwtype == L2TP_PWTYPE_ETH ? "ETH" : |
184 | session->pwtype == L2TP_PWTYPE_PPP ? "PPP" : |
185 | "" ); |
186 | if (session->send_seq || session->recv_seq) |
187 | seq_printf(m, fmt: " nr %u, ns %u\n" , session->nr, session->ns); |
188 | seq_printf(m, fmt: " refcnt %d\n" , refcount_read(r: &session->ref_count)); |
189 | seq_printf(m, fmt: " config 0/0/%c/%c/-/%s %08x %u\n" , |
190 | session->recv_seq ? 'R' : '-', |
191 | session->send_seq ? 'S' : '-', |
192 | session->lns_mode ? "LNS" : "LAC" , |
193 | 0, |
194 | jiffies_to_msecs(j: session->reorder_timeout)); |
195 | seq_printf(m, fmt: " offset 0 l2specific %hu/%d\n" , |
196 | session->l2specific_type, l2tp_get_l2specific_len(session)); |
197 | if (session->cookie_len) { |
198 | seq_printf(m, fmt: " cookie %02x%02x%02x%02x" , |
199 | session->cookie[0], session->cookie[1], |
200 | session->cookie[2], session->cookie[3]); |
201 | if (session->cookie_len == 8) |
202 | seq_printf(m, fmt: "%02x%02x%02x%02x" , |
203 | session->cookie[4], session->cookie[5], |
204 | session->cookie[6], session->cookie[7]); |
205 | seq_puts(m, s: "\n" ); |
206 | } |
207 | if (session->peer_cookie_len) { |
208 | seq_printf(m, fmt: " peer cookie %02x%02x%02x%02x" , |
209 | session->peer_cookie[0], session->peer_cookie[1], |
210 | session->peer_cookie[2], session->peer_cookie[3]); |
211 | if (session->peer_cookie_len == 8) |
212 | seq_printf(m, fmt: "%02x%02x%02x%02x" , |
213 | session->peer_cookie[4], session->peer_cookie[5], |
214 | session->peer_cookie[6], session->peer_cookie[7]); |
215 | seq_puts(m, s: "\n" ); |
216 | } |
217 | |
218 | seq_printf(m, fmt: " %u/%u tx %ld/%ld/%ld rx %ld/%ld/%ld\n" , |
219 | session->nr, session->ns, |
220 | atomic_long_read(v: &session->stats.tx_packets), |
221 | atomic_long_read(v: &session->stats.tx_bytes), |
222 | atomic_long_read(v: &session->stats.tx_errors), |
223 | atomic_long_read(v: &session->stats.rx_packets), |
224 | atomic_long_read(v: &session->stats.rx_bytes), |
225 | atomic_long_read(v: &session->stats.rx_errors)); |
226 | |
227 | if (session->show) |
228 | session->show(m, session); |
229 | } |
230 | |
231 | static int l2tp_dfs_seq_show(struct seq_file *m, void *v) |
232 | { |
233 | struct l2tp_dfs_seq_data *pd = v; |
234 | |
235 | /* display header on line 1 */ |
236 | if (v == SEQ_START_TOKEN) { |
237 | seq_puts(m, s: "TUNNEL ID, peer ID from IP to IP\n" ); |
238 | seq_puts(m, s: " L2TPv2/L2TPv3, UDP/IP\n" ); |
239 | seq_puts(m, s: " sessions session-count, refcnt refcnt/sk->refcnt\n" ); |
240 | seq_puts(m, s: " debug tx-pkts/bytes/errs rx-pkts/bytes/errs\n" ); |
241 | seq_puts(m, s: " SESSION ID, peer ID, PWTYPE\n" ); |
242 | seq_puts(m, s: " refcnt cnt\n" ); |
243 | seq_puts(m, s: " offset OFFSET l2specific TYPE/LEN\n" ); |
244 | seq_puts(m, s: " [ cookie ]\n" ); |
245 | seq_puts(m, s: " [ peer cookie ]\n" ); |
246 | seq_puts(m, s: " config mtu/mru/rcvseq/sendseq/dataseq/lns debug reorderto\n" ); |
247 | seq_puts(m, s: " nr/ns tx-pkts/bytes/errs rx-pkts/bytes/errs\n" ); |
248 | goto out; |
249 | } |
250 | |
251 | if (!pd->session) |
252 | l2tp_dfs_seq_tunnel_show(m, v: pd->tunnel); |
253 | else |
254 | l2tp_dfs_seq_session_show(m, v: pd->session); |
255 | |
256 | out: |
257 | return 0; |
258 | } |
259 | |
260 | static const struct seq_operations l2tp_dfs_seq_ops = { |
261 | .start = l2tp_dfs_seq_start, |
262 | .next = l2tp_dfs_seq_next, |
263 | .stop = l2tp_dfs_seq_stop, |
264 | .show = l2tp_dfs_seq_show, |
265 | }; |
266 | |
267 | static int l2tp_dfs_seq_open(struct inode *inode, struct file *file) |
268 | { |
269 | struct l2tp_dfs_seq_data *pd; |
270 | struct seq_file *seq; |
271 | int rc = -ENOMEM; |
272 | |
273 | pd = kzalloc(size: sizeof(*pd), GFP_KERNEL); |
274 | if (!pd) |
275 | goto out; |
276 | |
277 | /* Derive the network namespace from the pid opening the |
278 | * file. |
279 | */ |
280 | pd->net = get_net_ns_by_pid(current->pid); |
281 | if (IS_ERR(ptr: pd->net)) { |
282 | rc = PTR_ERR(ptr: pd->net); |
283 | goto err_free_pd; |
284 | } |
285 | netns_tracker_alloc(net: pd->net, tracker: &pd->ns_tracker, GFP_KERNEL); |
286 | rc = seq_open(file, &l2tp_dfs_seq_ops); |
287 | if (rc) |
288 | goto err_free_net; |
289 | |
290 | seq = file->private_data; |
291 | seq->private = pd; |
292 | |
293 | out: |
294 | return rc; |
295 | |
296 | err_free_net: |
297 | put_net_track(net: pd->net, tracker: &pd->ns_tracker); |
298 | err_free_pd: |
299 | kfree(objp: pd); |
300 | goto out; |
301 | } |
302 | |
303 | static int l2tp_dfs_seq_release(struct inode *inode, struct file *file) |
304 | { |
305 | struct l2tp_dfs_seq_data *pd; |
306 | struct seq_file *seq; |
307 | |
308 | seq = file->private_data; |
309 | pd = seq->private; |
310 | if (pd->net) |
311 | put_net_track(net: pd->net, tracker: &pd->ns_tracker); |
312 | kfree(objp: pd); |
313 | seq_release(inode, file); |
314 | |
315 | return 0; |
316 | } |
317 | |
318 | static const struct file_operations l2tp_dfs_fops = { |
319 | .owner = THIS_MODULE, |
320 | .open = l2tp_dfs_seq_open, |
321 | .read = seq_read, |
322 | .llseek = seq_lseek, |
323 | .release = l2tp_dfs_seq_release, |
324 | }; |
325 | |
326 | static int __init l2tp_debugfs_init(void) |
327 | { |
328 | rootdir = debugfs_create_dir(name: "l2tp" , NULL); |
329 | |
330 | debugfs_create_file(name: "tunnels" , mode: 0600, parent: rootdir, NULL, fops: &l2tp_dfs_fops); |
331 | |
332 | pr_info("L2TP debugfs support\n" ); |
333 | |
334 | return 0; |
335 | } |
336 | |
337 | static void __exit l2tp_debugfs_exit(void) |
338 | { |
339 | debugfs_remove_recursive(dentry: rootdir); |
340 | } |
341 | |
342 | module_init(l2tp_debugfs_init); |
343 | module_exit(l2tp_debugfs_exit); |
344 | |
345 | MODULE_LICENSE("GPL" ); |
346 | MODULE_AUTHOR("James Chapman <jchapman@katalix.com>" ); |
347 | MODULE_DESCRIPTION("L2TP debugfs driver" ); |
348 | MODULE_VERSION("1.0" ); |
349 | |