162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* L2TP subsystem debugfs
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (c) 2010 Katalix Systems Ltd
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/skbuff.h>
1162306a36Sopenharmony_ci#include <linux/socket.h>
1262306a36Sopenharmony_ci#include <linux/hash.h>
1362306a36Sopenharmony_ci#include <linux/l2tp.h>
1462306a36Sopenharmony_ci#include <linux/in.h>
1562306a36Sopenharmony_ci#include <linux/etherdevice.h>
1662306a36Sopenharmony_ci#include <linux/spinlock.h>
1762306a36Sopenharmony_ci#include <linux/debugfs.h>
1862306a36Sopenharmony_ci#include <net/sock.h>
1962306a36Sopenharmony_ci#include <net/ip.h>
2062306a36Sopenharmony_ci#include <net/icmp.h>
2162306a36Sopenharmony_ci#include <net/udp.h>
2262306a36Sopenharmony_ci#include <net/inet_common.h>
2362306a36Sopenharmony_ci#include <net/inet_hashtables.h>
2462306a36Sopenharmony_ci#include <net/tcp_states.h>
2562306a36Sopenharmony_ci#include <net/protocol.h>
2662306a36Sopenharmony_ci#include <net/xfrm.h>
2762306a36Sopenharmony_ci#include <net/net_namespace.h>
2862306a36Sopenharmony_ci#include <net/netns/generic.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#include "l2tp_core.h"
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic struct dentry *rootdir;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistruct l2tp_dfs_seq_data {
3562306a36Sopenharmony_ci	struct net	*net;
3662306a36Sopenharmony_ci	netns_tracker	ns_tracker;
3762306a36Sopenharmony_ci	int tunnel_idx;			/* current tunnel */
3862306a36Sopenharmony_ci	int session_idx;		/* index of session within current tunnel */
3962306a36Sopenharmony_ci	struct l2tp_tunnel *tunnel;
4062306a36Sopenharmony_ci	struct l2tp_session *session;	/* NULL means get next tunnel */
4162306a36Sopenharmony_ci};
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic void l2tp_dfs_next_tunnel(struct l2tp_dfs_seq_data *pd)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	/* Drop reference taken during previous invocation */
4662306a36Sopenharmony_ci	if (pd->tunnel)
4762306a36Sopenharmony_ci		l2tp_tunnel_dec_refcount(pd->tunnel);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	pd->tunnel = l2tp_tunnel_get_nth(pd->net, pd->tunnel_idx);
5062306a36Sopenharmony_ci	pd->tunnel_idx++;
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic void l2tp_dfs_next_session(struct l2tp_dfs_seq_data *pd)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	/* Drop reference taken during previous invocation */
5662306a36Sopenharmony_ci	if (pd->session)
5762306a36Sopenharmony_ci		l2tp_session_dec_refcount(pd->session);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	pd->session = l2tp_session_get_nth(pd->tunnel, pd->session_idx);
6062306a36Sopenharmony_ci	pd->session_idx++;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (!pd->session) {
6362306a36Sopenharmony_ci		pd->session_idx = 0;
6462306a36Sopenharmony_ci		l2tp_dfs_next_tunnel(pd);
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic void *l2tp_dfs_seq_start(struct seq_file *m, loff_t *offs)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	struct l2tp_dfs_seq_data *pd = SEQ_START_TOKEN;
7162306a36Sopenharmony_ci	loff_t pos = *offs;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (!pos)
7462306a36Sopenharmony_ci		goto out;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	if (WARN_ON(!m->private)) {
7762306a36Sopenharmony_ci		pd = NULL;
7862306a36Sopenharmony_ci		goto out;
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci	pd = m->private;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	if (!pd->tunnel)
8362306a36Sopenharmony_ci		l2tp_dfs_next_tunnel(pd);
8462306a36Sopenharmony_ci	else
8562306a36Sopenharmony_ci		l2tp_dfs_next_session(pd);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	/* NULL tunnel and session indicates end of list */
8862306a36Sopenharmony_ci	if (!pd->tunnel && !pd->session)
8962306a36Sopenharmony_ci		pd = NULL;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ciout:
9262306a36Sopenharmony_ci	return pd;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic void *l2tp_dfs_seq_next(struct seq_file *m, void *v, loff_t *pos)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	(*pos)++;
9862306a36Sopenharmony_ci	return NULL;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic void l2tp_dfs_seq_stop(struct seq_file *p, void *v)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	struct l2tp_dfs_seq_data *pd = v;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	if (!pd || pd == SEQ_START_TOKEN)
10662306a36Sopenharmony_ci		return;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	/* Drop reference taken by last invocation of l2tp_dfs_next_session()
10962306a36Sopenharmony_ci	 * or l2tp_dfs_next_tunnel().
11062306a36Sopenharmony_ci	 */
11162306a36Sopenharmony_ci	if (pd->session) {
11262306a36Sopenharmony_ci		l2tp_session_dec_refcount(pd->session);
11362306a36Sopenharmony_ci		pd->session = NULL;
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci	if (pd->tunnel) {
11662306a36Sopenharmony_ci		l2tp_tunnel_dec_refcount(pd->tunnel);
11762306a36Sopenharmony_ci		pd->tunnel = NULL;
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic void l2tp_dfs_seq_tunnel_show(struct seq_file *m, void *v)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	struct l2tp_tunnel *tunnel = v;
12462306a36Sopenharmony_ci	struct l2tp_session *session;
12562306a36Sopenharmony_ci	int session_count = 0;
12662306a36Sopenharmony_ci	int hash;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	rcu_read_lock_bh();
12962306a36Sopenharmony_ci	for (hash = 0; hash < L2TP_HASH_SIZE; hash++) {
13062306a36Sopenharmony_ci		hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) {
13162306a36Sopenharmony_ci			/* Session ID of zero is a dummy/reserved value used by pppol2tp */
13262306a36Sopenharmony_ci			if (session->session_id == 0)
13362306a36Sopenharmony_ci				continue;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci			session_count++;
13662306a36Sopenharmony_ci		}
13762306a36Sopenharmony_ci	}
13862306a36Sopenharmony_ci	rcu_read_unlock_bh();
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	seq_printf(m, "\nTUNNEL %u peer %u", tunnel->tunnel_id, tunnel->peer_tunnel_id);
14162306a36Sopenharmony_ci	if (tunnel->sock) {
14262306a36Sopenharmony_ci		struct inet_sock *inet = inet_sk(tunnel->sock);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
14562306a36Sopenharmony_ci		if (tunnel->sock->sk_family == AF_INET6) {
14662306a36Sopenharmony_ci			const struct ipv6_pinfo *np = inet6_sk(tunnel->sock);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci			seq_printf(m, " from %pI6c to %pI6c\n",
14962306a36Sopenharmony_ci				   &np->saddr, &tunnel->sock->sk_v6_daddr);
15062306a36Sopenharmony_ci		}
15162306a36Sopenharmony_ci#endif
15262306a36Sopenharmony_ci		if (tunnel->sock->sk_family == AF_INET)
15362306a36Sopenharmony_ci			seq_printf(m, " from %pI4 to %pI4\n",
15462306a36Sopenharmony_ci				   &inet->inet_saddr, &inet->inet_daddr);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci		if (tunnel->encap == L2TP_ENCAPTYPE_UDP)
15762306a36Sopenharmony_ci			seq_printf(m, " source port %hu, dest port %hu\n",
15862306a36Sopenharmony_ci				   ntohs(inet->inet_sport), ntohs(inet->inet_dport));
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci	seq_printf(m, " L2TPv%d, %s\n", tunnel->version,
16162306a36Sopenharmony_ci		   tunnel->encap == L2TP_ENCAPTYPE_UDP ? "UDP" :
16262306a36Sopenharmony_ci		   tunnel->encap == L2TP_ENCAPTYPE_IP ? "IP" :
16362306a36Sopenharmony_ci		   "");
16462306a36Sopenharmony_ci	seq_printf(m, " %d sessions, refcnt %d/%d\n", session_count,
16562306a36Sopenharmony_ci		   tunnel->sock ? refcount_read(&tunnel->sock->sk_refcnt) : 0,
16662306a36Sopenharmony_ci		   refcount_read(&tunnel->ref_count));
16762306a36Sopenharmony_ci	seq_printf(m, " %08x rx %ld/%ld/%ld rx %ld/%ld/%ld\n",
16862306a36Sopenharmony_ci		   0,
16962306a36Sopenharmony_ci		   atomic_long_read(&tunnel->stats.tx_packets),
17062306a36Sopenharmony_ci		   atomic_long_read(&tunnel->stats.tx_bytes),
17162306a36Sopenharmony_ci		   atomic_long_read(&tunnel->stats.tx_errors),
17262306a36Sopenharmony_ci		   atomic_long_read(&tunnel->stats.rx_packets),
17362306a36Sopenharmony_ci		   atomic_long_read(&tunnel->stats.rx_bytes),
17462306a36Sopenharmony_ci		   atomic_long_read(&tunnel->stats.rx_errors));
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic void l2tp_dfs_seq_session_show(struct seq_file *m, void *v)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	struct l2tp_session *session = v;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	seq_printf(m, "  SESSION %u, peer %u, %s\n", session->session_id,
18262306a36Sopenharmony_ci		   session->peer_session_id,
18362306a36Sopenharmony_ci		   session->pwtype == L2TP_PWTYPE_ETH ? "ETH" :
18462306a36Sopenharmony_ci		   session->pwtype == L2TP_PWTYPE_PPP ? "PPP" :
18562306a36Sopenharmony_ci		   "");
18662306a36Sopenharmony_ci	if (session->send_seq || session->recv_seq)
18762306a36Sopenharmony_ci		seq_printf(m, "   nr %u, ns %u\n", session->nr, session->ns);
18862306a36Sopenharmony_ci	seq_printf(m, "   refcnt %d\n", refcount_read(&session->ref_count));
18962306a36Sopenharmony_ci	seq_printf(m, "   config 0/0/%c/%c/-/%s %08x %u\n",
19062306a36Sopenharmony_ci		   session->recv_seq ? 'R' : '-',
19162306a36Sopenharmony_ci		   session->send_seq ? 'S' : '-',
19262306a36Sopenharmony_ci		   session->lns_mode ? "LNS" : "LAC",
19362306a36Sopenharmony_ci		   0,
19462306a36Sopenharmony_ci		   jiffies_to_msecs(session->reorder_timeout));
19562306a36Sopenharmony_ci	seq_printf(m, "   offset 0 l2specific %hu/%d\n",
19662306a36Sopenharmony_ci		   session->l2specific_type, l2tp_get_l2specific_len(session));
19762306a36Sopenharmony_ci	if (session->cookie_len) {
19862306a36Sopenharmony_ci		seq_printf(m, "   cookie %02x%02x%02x%02x",
19962306a36Sopenharmony_ci			   session->cookie[0], session->cookie[1],
20062306a36Sopenharmony_ci			   session->cookie[2], session->cookie[3]);
20162306a36Sopenharmony_ci		if (session->cookie_len == 8)
20262306a36Sopenharmony_ci			seq_printf(m, "%02x%02x%02x%02x",
20362306a36Sopenharmony_ci				   session->cookie[4], session->cookie[5],
20462306a36Sopenharmony_ci				   session->cookie[6], session->cookie[7]);
20562306a36Sopenharmony_ci		seq_puts(m, "\n");
20662306a36Sopenharmony_ci	}
20762306a36Sopenharmony_ci	if (session->peer_cookie_len) {
20862306a36Sopenharmony_ci		seq_printf(m, "   peer cookie %02x%02x%02x%02x",
20962306a36Sopenharmony_ci			   session->peer_cookie[0], session->peer_cookie[1],
21062306a36Sopenharmony_ci			   session->peer_cookie[2], session->peer_cookie[3]);
21162306a36Sopenharmony_ci		if (session->peer_cookie_len == 8)
21262306a36Sopenharmony_ci			seq_printf(m, "%02x%02x%02x%02x",
21362306a36Sopenharmony_ci				   session->peer_cookie[4], session->peer_cookie[5],
21462306a36Sopenharmony_ci				   session->peer_cookie[6], session->peer_cookie[7]);
21562306a36Sopenharmony_ci		seq_puts(m, "\n");
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	seq_printf(m, "   %u/%u tx %ld/%ld/%ld rx %ld/%ld/%ld\n",
21962306a36Sopenharmony_ci		   session->nr, session->ns,
22062306a36Sopenharmony_ci		   atomic_long_read(&session->stats.tx_packets),
22162306a36Sopenharmony_ci		   atomic_long_read(&session->stats.tx_bytes),
22262306a36Sopenharmony_ci		   atomic_long_read(&session->stats.tx_errors),
22362306a36Sopenharmony_ci		   atomic_long_read(&session->stats.rx_packets),
22462306a36Sopenharmony_ci		   atomic_long_read(&session->stats.rx_bytes),
22562306a36Sopenharmony_ci		   atomic_long_read(&session->stats.rx_errors));
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	if (session->show)
22862306a36Sopenharmony_ci		session->show(m, session);
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic int l2tp_dfs_seq_show(struct seq_file *m, void *v)
23262306a36Sopenharmony_ci{
23362306a36Sopenharmony_ci	struct l2tp_dfs_seq_data *pd = v;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	/* display header on line 1 */
23662306a36Sopenharmony_ci	if (v == SEQ_START_TOKEN) {
23762306a36Sopenharmony_ci		seq_puts(m, "TUNNEL ID, peer ID from IP to IP\n");
23862306a36Sopenharmony_ci		seq_puts(m, " L2TPv2/L2TPv3, UDP/IP\n");
23962306a36Sopenharmony_ci		seq_puts(m, " sessions session-count, refcnt refcnt/sk->refcnt\n");
24062306a36Sopenharmony_ci		seq_puts(m, " debug tx-pkts/bytes/errs rx-pkts/bytes/errs\n");
24162306a36Sopenharmony_ci		seq_puts(m, "  SESSION ID, peer ID, PWTYPE\n");
24262306a36Sopenharmony_ci		seq_puts(m, "   refcnt cnt\n");
24362306a36Sopenharmony_ci		seq_puts(m, "   offset OFFSET l2specific TYPE/LEN\n");
24462306a36Sopenharmony_ci		seq_puts(m, "   [ cookie ]\n");
24562306a36Sopenharmony_ci		seq_puts(m, "   [ peer cookie ]\n");
24662306a36Sopenharmony_ci		seq_puts(m, "   config mtu/mru/rcvseq/sendseq/dataseq/lns debug reorderto\n");
24762306a36Sopenharmony_ci		seq_puts(m, "   nr/ns tx-pkts/bytes/errs rx-pkts/bytes/errs\n");
24862306a36Sopenharmony_ci		goto out;
24962306a36Sopenharmony_ci	}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	if (!pd->session)
25262306a36Sopenharmony_ci		l2tp_dfs_seq_tunnel_show(m, pd->tunnel);
25362306a36Sopenharmony_ci	else
25462306a36Sopenharmony_ci		l2tp_dfs_seq_session_show(m, pd->session);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ciout:
25762306a36Sopenharmony_ci	return 0;
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_cistatic const struct seq_operations l2tp_dfs_seq_ops = {
26162306a36Sopenharmony_ci	.start		= l2tp_dfs_seq_start,
26262306a36Sopenharmony_ci	.next		= l2tp_dfs_seq_next,
26362306a36Sopenharmony_ci	.stop		= l2tp_dfs_seq_stop,
26462306a36Sopenharmony_ci	.show		= l2tp_dfs_seq_show,
26562306a36Sopenharmony_ci};
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic int l2tp_dfs_seq_open(struct inode *inode, struct file *file)
26862306a36Sopenharmony_ci{
26962306a36Sopenharmony_ci	struct l2tp_dfs_seq_data *pd;
27062306a36Sopenharmony_ci	struct seq_file *seq;
27162306a36Sopenharmony_ci	int rc = -ENOMEM;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	pd = kzalloc(sizeof(*pd), GFP_KERNEL);
27462306a36Sopenharmony_ci	if (!pd)
27562306a36Sopenharmony_ci		goto out;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	/* Derive the network namespace from the pid opening the
27862306a36Sopenharmony_ci	 * file.
27962306a36Sopenharmony_ci	 */
28062306a36Sopenharmony_ci	pd->net = get_net_ns_by_pid(current->pid);
28162306a36Sopenharmony_ci	if (IS_ERR(pd->net)) {
28262306a36Sopenharmony_ci		rc = PTR_ERR(pd->net);
28362306a36Sopenharmony_ci		goto err_free_pd;
28462306a36Sopenharmony_ci	}
28562306a36Sopenharmony_ci	netns_tracker_alloc(pd->net, &pd->ns_tracker, GFP_KERNEL);
28662306a36Sopenharmony_ci	rc = seq_open(file, &l2tp_dfs_seq_ops);
28762306a36Sopenharmony_ci	if (rc)
28862306a36Sopenharmony_ci		goto err_free_net;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	seq = file->private_data;
29162306a36Sopenharmony_ci	seq->private = pd;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ciout:
29462306a36Sopenharmony_ci	return rc;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cierr_free_net:
29762306a36Sopenharmony_ci	put_net_track(pd->net, &pd->ns_tracker);
29862306a36Sopenharmony_cierr_free_pd:
29962306a36Sopenharmony_ci	kfree(pd);
30062306a36Sopenharmony_ci	goto out;
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic int l2tp_dfs_seq_release(struct inode *inode, struct file *file)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	struct l2tp_dfs_seq_data *pd;
30662306a36Sopenharmony_ci	struct seq_file *seq;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	seq = file->private_data;
30962306a36Sopenharmony_ci	pd = seq->private;
31062306a36Sopenharmony_ci	if (pd->net)
31162306a36Sopenharmony_ci		put_net_track(pd->net, &pd->ns_tracker);
31262306a36Sopenharmony_ci	kfree(pd);
31362306a36Sopenharmony_ci	seq_release(inode, file);
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	return 0;
31662306a36Sopenharmony_ci}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_cistatic const struct file_operations l2tp_dfs_fops = {
31962306a36Sopenharmony_ci	.owner		= THIS_MODULE,
32062306a36Sopenharmony_ci	.open		= l2tp_dfs_seq_open,
32162306a36Sopenharmony_ci	.read		= seq_read,
32262306a36Sopenharmony_ci	.llseek		= seq_lseek,
32362306a36Sopenharmony_ci	.release	= l2tp_dfs_seq_release,
32462306a36Sopenharmony_ci};
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cistatic int __init l2tp_debugfs_init(void)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	rootdir = debugfs_create_dir("l2tp", NULL);
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	debugfs_create_file("tunnels", 0600, rootdir, NULL, &l2tp_dfs_fops);
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	pr_info("L2TP debugfs support\n");
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	return 0;
33562306a36Sopenharmony_ci}
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_cistatic void __exit l2tp_debugfs_exit(void)
33862306a36Sopenharmony_ci{
33962306a36Sopenharmony_ci	debugfs_remove_recursive(rootdir);
34062306a36Sopenharmony_ci}
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_cimodule_init(l2tp_debugfs_init);
34362306a36Sopenharmony_cimodule_exit(l2tp_debugfs_exit);
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
34662306a36Sopenharmony_ciMODULE_AUTHOR("James Chapman <jchapman@katalix.com>");
34762306a36Sopenharmony_ciMODULE_DESCRIPTION("L2TP debugfs driver");
34862306a36Sopenharmony_ciMODULE_VERSION("1.0");
349