xref: /linux/net/l2tp/l2tp_netlink.c (revision f5bb341e1d1af7722a73fa8c96ca8c9a91f85e5b)
1309795f4SJames Chapman /*
2309795f4SJames Chapman  * L2TP netlink layer, for management
3309795f4SJames Chapman  *
4309795f4SJames Chapman  * Copyright (c) 2008,2009,2010 Katalix Systems Ltd
5309795f4SJames Chapman  *
6309795f4SJames Chapman  * Partly based on the IrDA nelink implementation
7309795f4SJames Chapman  * (see net/irda/irnetlink.c) which is:
8309795f4SJames Chapman  * Copyright (c) 2007 Samuel Ortiz <samuel@sortiz.org>
9309795f4SJames Chapman  * which is in turn partly based on the wireless netlink code:
10309795f4SJames Chapman  * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
11309795f4SJames Chapman  *
12309795f4SJames Chapman  * This program is free software; you can redistribute it and/or modify
13309795f4SJames Chapman  * it under the terms of the GNU General Public License version 2 as
14309795f4SJames Chapman  * published by the Free Software Foundation.
15309795f4SJames Chapman  */
16309795f4SJames Chapman 
17a4ca44faSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18a4ca44faSJoe Perches 
19309795f4SJames Chapman #include <net/sock.h>
20309795f4SJames Chapman #include <net/genetlink.h>
21309795f4SJames Chapman #include <net/udp.h>
22309795f4SJames Chapman #include <linux/in.h>
23309795f4SJames Chapman #include <linux/udp.h>
24309795f4SJames Chapman #include <linux/socket.h>
25309795f4SJames Chapman #include <linux/module.h>
26309795f4SJames Chapman #include <linux/list.h>
27309795f4SJames Chapman #include <net/net_namespace.h>
28309795f4SJames Chapman 
29309795f4SJames Chapman #include <linux/l2tp.h>
30309795f4SJames Chapman 
31309795f4SJames Chapman #include "l2tp_core.h"
32309795f4SJames Chapman 
33309795f4SJames Chapman 
34309795f4SJames Chapman static struct genl_family l2tp_nl_family = {
35309795f4SJames Chapman 	.id		= GENL_ID_GENERATE,
36309795f4SJames Chapman 	.name		= L2TP_GENL_NAME,
37309795f4SJames Chapman 	.version	= L2TP_GENL_VERSION,
38309795f4SJames Chapman 	.hdrsize	= 0,
39309795f4SJames Chapman 	.maxattr	= L2TP_ATTR_MAX,
40b6fdfdfaSTom Parkin 	.netnsok	= true,
41309795f4SJames Chapman };
42309795f4SJames Chapman 
4333f72e6fSBill Hong static const struct genl_multicast_group l2tp_multicast_group[] = {
4433f72e6fSBill Hong 	{
4533f72e6fSBill Hong 		.name = L2TP_GENL_MCGROUP,
4633f72e6fSBill Hong 	},
4733f72e6fSBill Hong };
4833f72e6fSBill Hong 
4933f72e6fSBill Hong static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 portid, u32 seq,
5033f72e6fSBill Hong 			       int flags, struct l2tp_tunnel *tunnel, u8 cmd);
5133f72e6fSBill Hong static int l2tp_nl_session_send(struct sk_buff *skb, u32 portid, u32 seq,
5233f72e6fSBill Hong 				int flags, struct l2tp_session *session,
5333f72e6fSBill Hong 				u8 cmd);
5433f72e6fSBill Hong 
55309795f4SJames Chapman /* Accessed under genl lock */
56309795f4SJames Chapman static const struct l2tp_nl_cmd_ops *l2tp_nl_cmd_ops[__L2TP_PWTYPE_MAX];
57309795f4SJames Chapman 
58309795f4SJames Chapman static struct l2tp_session *l2tp_nl_session_find(struct genl_info *info)
59309795f4SJames Chapman {
60309795f4SJames Chapman 	u32 tunnel_id;
61309795f4SJames Chapman 	u32 session_id;
62309795f4SJames Chapman 	char *ifname;
63309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
64309795f4SJames Chapman 	struct l2tp_session *session = NULL;
65309795f4SJames Chapman 	struct net *net = genl_info_net(info);
66309795f4SJames Chapman 
67309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_IFNAME]) {
68309795f4SJames Chapman 		ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]);
69309795f4SJames Chapman 		session = l2tp_session_find_by_ifname(net, ifname);
70309795f4SJames Chapman 	} else if ((info->attrs[L2TP_ATTR_SESSION_ID]) &&
71309795f4SJames Chapman 		   (info->attrs[L2TP_ATTR_CONN_ID])) {
72309795f4SJames Chapman 		tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
73309795f4SJames Chapman 		session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]);
74309795f4SJames Chapman 		tunnel = l2tp_tunnel_find(net, tunnel_id);
75309795f4SJames Chapman 		if (tunnel)
76309795f4SJames Chapman 			session = l2tp_session_find(net, tunnel, session_id);
77309795f4SJames Chapman 	}
78309795f4SJames Chapman 
79309795f4SJames Chapman 	return session;
80309795f4SJames Chapman }
81309795f4SJames Chapman 
82309795f4SJames Chapman static int l2tp_nl_cmd_noop(struct sk_buff *skb, struct genl_info *info)
83309795f4SJames Chapman {
84309795f4SJames Chapman 	struct sk_buff *msg;
85309795f4SJames Chapman 	void *hdr;
86309795f4SJames Chapman 	int ret = -ENOBUFS;
87309795f4SJames Chapman 
8858050fceSThomas Graf 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
89309795f4SJames Chapman 	if (!msg) {
90309795f4SJames Chapman 		ret = -ENOMEM;
91309795f4SJames Chapman 		goto out;
92309795f4SJames Chapman 	}
93309795f4SJames Chapman 
9415e47304SEric W. Biederman 	hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
95309795f4SJames Chapman 			  &l2tp_nl_family, 0, L2TP_CMD_NOOP);
967f8436a1SWei Yongjun 	if (!hdr) {
977f8436a1SWei Yongjun 		ret = -EMSGSIZE;
98309795f4SJames Chapman 		goto err_out;
99309795f4SJames Chapman 	}
100309795f4SJames Chapman 
101309795f4SJames Chapman 	genlmsg_end(msg, hdr);
102309795f4SJames Chapman 
10315e47304SEric W. Biederman 	return genlmsg_unicast(genl_info_net(info), msg, info->snd_portid);
104309795f4SJames Chapman 
105309795f4SJames Chapman err_out:
106309795f4SJames Chapman 	nlmsg_free(msg);
107309795f4SJames Chapman 
108309795f4SJames Chapman out:
109309795f4SJames Chapman 	return ret;
110309795f4SJames Chapman }
111309795f4SJames Chapman 
11233f72e6fSBill Hong static int l2tp_tunnel_notify(struct genl_family *family,
11333f72e6fSBill Hong 			      struct genl_info *info,
11433f72e6fSBill Hong 			      struct l2tp_tunnel *tunnel,
11533f72e6fSBill Hong 			      u8 cmd)
11633f72e6fSBill Hong {
11733f72e6fSBill Hong 	struct sk_buff *msg;
11833f72e6fSBill Hong 	int ret;
11933f72e6fSBill Hong 
12033f72e6fSBill Hong 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
12133f72e6fSBill Hong 	if (!msg)
12233f72e6fSBill Hong 		return -ENOMEM;
12333f72e6fSBill Hong 
12433f72e6fSBill Hong 	ret = l2tp_nl_tunnel_send(msg, info->snd_portid, info->snd_seq,
12533f72e6fSBill Hong 				  NLM_F_ACK, tunnel, cmd);
12633f72e6fSBill Hong 
127853effc5SMark Tomlinson 	if (ret >= 0) {
128853effc5SMark Tomlinson 		ret = genlmsg_multicast_allns(family, msg, 0, 0, GFP_ATOMIC);
129853effc5SMark Tomlinson 		/* We don't care if no one is listening */
130853effc5SMark Tomlinson 		if (ret == -ESRCH)
131853effc5SMark Tomlinson 			ret = 0;
132853effc5SMark Tomlinson 		return ret;
133853effc5SMark Tomlinson 	}
13433f72e6fSBill Hong 
13533f72e6fSBill Hong 	nlmsg_free(msg);
13633f72e6fSBill Hong 
13733f72e6fSBill Hong 	return ret;
13833f72e6fSBill Hong }
13933f72e6fSBill Hong 
14033f72e6fSBill Hong static int l2tp_session_notify(struct genl_family *family,
14133f72e6fSBill Hong 			       struct genl_info *info,
14233f72e6fSBill Hong 			       struct l2tp_session *session,
14333f72e6fSBill Hong 			       u8 cmd)
14433f72e6fSBill Hong {
14533f72e6fSBill Hong 	struct sk_buff *msg;
14633f72e6fSBill Hong 	int ret;
14733f72e6fSBill Hong 
14833f72e6fSBill Hong 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
14933f72e6fSBill Hong 	if (!msg)
15033f72e6fSBill Hong 		return -ENOMEM;
15133f72e6fSBill Hong 
15233f72e6fSBill Hong 	ret = l2tp_nl_session_send(msg, info->snd_portid, info->snd_seq,
15333f72e6fSBill Hong 				   NLM_F_ACK, session, cmd);
15433f72e6fSBill Hong 
155853effc5SMark Tomlinson 	if (ret >= 0) {
156853effc5SMark Tomlinson 		ret = genlmsg_multicast_allns(family, msg, 0, 0, GFP_ATOMIC);
157853effc5SMark Tomlinson 		/* We don't care if no one is listening */
158853effc5SMark Tomlinson 		if (ret == -ESRCH)
159853effc5SMark Tomlinson 			ret = 0;
160853effc5SMark Tomlinson 		return ret;
161853effc5SMark Tomlinson 	}
16233f72e6fSBill Hong 
16333f72e6fSBill Hong 	nlmsg_free(msg);
16433f72e6fSBill Hong 
16533f72e6fSBill Hong 	return ret;
16633f72e6fSBill Hong }
16733f72e6fSBill Hong 
168309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info)
169309795f4SJames Chapman {
170309795f4SJames Chapman 	u32 tunnel_id;
171309795f4SJames Chapman 	u32 peer_tunnel_id;
172309795f4SJames Chapman 	int proto_version;
173309795f4SJames Chapman 	int fd;
174309795f4SJames Chapman 	int ret = 0;
175309795f4SJames Chapman 	struct l2tp_tunnel_cfg cfg = { 0, };
176309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
177309795f4SJames Chapman 	struct net *net = genl_info_net(info);
178309795f4SJames Chapman 
179309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_CONN_ID]) {
180309795f4SJames Chapman 		ret = -EINVAL;
181309795f4SJames Chapman 		goto out;
182309795f4SJames Chapman 	}
183309795f4SJames Chapman 	tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
184309795f4SJames Chapman 
185309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_PEER_CONN_ID]) {
186309795f4SJames Chapman 		ret = -EINVAL;
187309795f4SJames Chapman 		goto out;
188309795f4SJames Chapman 	}
189309795f4SJames Chapman 	peer_tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_PEER_CONN_ID]);
190309795f4SJames Chapman 
191309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_PROTO_VERSION]) {
192309795f4SJames Chapman 		ret = -EINVAL;
193309795f4SJames Chapman 		goto out;
194309795f4SJames Chapman 	}
195309795f4SJames Chapman 	proto_version = nla_get_u8(info->attrs[L2TP_ATTR_PROTO_VERSION]);
196309795f4SJames Chapman 
197309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_ENCAP_TYPE]) {
198309795f4SJames Chapman 		ret = -EINVAL;
199309795f4SJames Chapman 		goto out;
200309795f4SJames Chapman 	}
201309795f4SJames Chapman 	cfg.encap = nla_get_u16(info->attrs[L2TP_ATTR_ENCAP_TYPE]);
202309795f4SJames Chapman 
203789a4a2cSJames Chapman 	fd = -1;
204789a4a2cSJames Chapman 	if (info->attrs[L2TP_ATTR_FD]) {
205309795f4SJames Chapman 		fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]);
206789a4a2cSJames Chapman 	} else {
207f9bac8dfSChris Elston #if IS_ENABLED(CONFIG_IPV6)
208f9bac8dfSChris Elston 		if (info->attrs[L2TP_ATTR_IP6_SADDR] &&
209f9bac8dfSChris Elston 		    info->attrs[L2TP_ATTR_IP6_DADDR]) {
210f9bac8dfSChris Elston 			cfg.local_ip6 = nla_data(
211f9bac8dfSChris Elston 				info->attrs[L2TP_ATTR_IP6_SADDR]);
212f9bac8dfSChris Elston 			cfg.peer_ip6 = nla_data(
213f9bac8dfSChris Elston 				info->attrs[L2TP_ATTR_IP6_DADDR]);
214f9bac8dfSChris Elston 		} else
215f9bac8dfSChris Elston #endif
216f9bac8dfSChris Elston 		if (info->attrs[L2TP_ATTR_IP_SADDR] &&
217f9bac8dfSChris Elston 		    info->attrs[L2TP_ATTR_IP_DADDR]) {
21867b61f6cSJiri Benc 			cfg.local_ip.s_addr = nla_get_in_addr(
219f9bac8dfSChris Elston 				info->attrs[L2TP_ATTR_IP_SADDR]);
22067b61f6cSJiri Benc 			cfg.peer_ip.s_addr = nla_get_in_addr(
221f9bac8dfSChris Elston 				info->attrs[L2TP_ATTR_IP_DADDR]);
222f9bac8dfSChris Elston 		} else {
223f9bac8dfSChris Elston 			ret = -EINVAL;
224f9bac8dfSChris Elston 			goto out;
225f9bac8dfSChris Elston 		}
226789a4a2cSJames Chapman 		if (info->attrs[L2TP_ATTR_UDP_SPORT])
227789a4a2cSJames Chapman 			cfg.local_udp_port = nla_get_u16(info->attrs[L2TP_ATTR_UDP_SPORT]);
228789a4a2cSJames Chapman 		if (info->attrs[L2TP_ATTR_UDP_DPORT])
229789a4a2cSJames Chapman 			cfg.peer_udp_port = nla_get_u16(info->attrs[L2TP_ATTR_UDP_DPORT]);
230789a4a2cSJames Chapman 		if (info->attrs[L2TP_ATTR_UDP_CSUM])
231789a4a2cSJames Chapman 			cfg.use_udp_checksums = nla_get_flag(info->attrs[L2TP_ATTR_UDP_CSUM]);
2326b649feaSTom Herbert 
2336b649feaSTom Herbert #if IS_ENABLED(CONFIG_IPV6)
2346b649feaSTom Herbert 		if (info->attrs[L2TP_ATTR_UDP_ZERO_CSUM6_TX])
2356b649feaSTom Herbert 			cfg.udp6_zero_tx_checksums = nla_get_flag(info->attrs[L2TP_ATTR_UDP_ZERO_CSUM6_TX]);
2366b649feaSTom Herbert 		if (info->attrs[L2TP_ATTR_UDP_ZERO_CSUM6_RX])
2376b649feaSTom Herbert 			cfg.udp6_zero_rx_checksums = nla_get_flag(info->attrs[L2TP_ATTR_UDP_ZERO_CSUM6_RX]);
2386b649feaSTom Herbert #endif
239789a4a2cSJames Chapman 	}
240309795f4SJames Chapman 
241309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_DEBUG])
242309795f4SJames Chapman 		cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
243309795f4SJames Chapman 
244309795f4SJames Chapman 	tunnel = l2tp_tunnel_find(net, tunnel_id);
245309795f4SJames Chapman 	if (tunnel != NULL) {
246309795f4SJames Chapman 		ret = -EEXIST;
247309795f4SJames Chapman 		goto out;
248309795f4SJames Chapman 	}
249309795f4SJames Chapman 
250309795f4SJames Chapman 	ret = -EINVAL;
251309795f4SJames Chapman 	switch (cfg.encap) {
252309795f4SJames Chapman 	case L2TP_ENCAPTYPE_UDP:
253309795f4SJames Chapman 	case L2TP_ENCAPTYPE_IP:
254309795f4SJames Chapman 		ret = l2tp_tunnel_create(net, fd, proto_version, tunnel_id,
255309795f4SJames Chapman 					 peer_tunnel_id, &cfg, &tunnel);
256309795f4SJames Chapman 		break;
257309795f4SJames Chapman 	}
258309795f4SJames Chapman 
25933f72e6fSBill Hong 	if (ret >= 0)
26033f72e6fSBill Hong 		ret = l2tp_tunnel_notify(&l2tp_nl_family, info,
26133f72e6fSBill Hong 					 tunnel, L2TP_CMD_TUNNEL_CREATE);
262309795f4SJames Chapman out:
263309795f4SJames Chapman 	return ret;
264309795f4SJames Chapman }
265309795f4SJames Chapman 
266309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_delete(struct sk_buff *skb, struct genl_info *info)
267309795f4SJames Chapman {
268309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
269309795f4SJames Chapman 	u32 tunnel_id;
270309795f4SJames Chapman 	int ret = 0;
271309795f4SJames Chapman 	struct net *net = genl_info_net(info);
272309795f4SJames Chapman 
273309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_CONN_ID]) {
274309795f4SJames Chapman 		ret = -EINVAL;
275309795f4SJames Chapman 		goto out;
276309795f4SJames Chapman 	}
277309795f4SJames Chapman 	tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
278309795f4SJames Chapman 
279309795f4SJames Chapman 	tunnel = l2tp_tunnel_find(net, tunnel_id);
280309795f4SJames Chapman 	if (tunnel == NULL) {
281309795f4SJames Chapman 		ret = -ENODEV;
282309795f4SJames Chapman 		goto out;
283309795f4SJames Chapman 	}
284309795f4SJames Chapman 
28533f72e6fSBill Hong 	l2tp_tunnel_notify(&l2tp_nl_family, info,
28633f72e6fSBill Hong 			   tunnel, L2TP_CMD_TUNNEL_DELETE);
28733f72e6fSBill Hong 
288309795f4SJames Chapman 	(void) l2tp_tunnel_delete(tunnel);
289309795f4SJames Chapman 
290309795f4SJames Chapman out:
291309795f4SJames Chapman 	return ret;
292309795f4SJames Chapman }
293309795f4SJames Chapman 
294309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_modify(struct sk_buff *skb, struct genl_info *info)
295309795f4SJames Chapman {
296309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
297309795f4SJames Chapman 	u32 tunnel_id;
298309795f4SJames Chapman 	int ret = 0;
299309795f4SJames Chapman 	struct net *net = genl_info_net(info);
300309795f4SJames Chapman 
301309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_CONN_ID]) {
302309795f4SJames Chapman 		ret = -EINVAL;
303309795f4SJames Chapman 		goto out;
304309795f4SJames Chapman 	}
305309795f4SJames Chapman 	tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
306309795f4SJames Chapman 
307309795f4SJames Chapman 	tunnel = l2tp_tunnel_find(net, tunnel_id);
308309795f4SJames Chapman 	if (tunnel == NULL) {
309309795f4SJames Chapman 		ret = -ENODEV;
310309795f4SJames Chapman 		goto out;
311309795f4SJames Chapman 	}
312309795f4SJames Chapman 
313309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_DEBUG])
314309795f4SJames Chapman 		tunnel->debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
315309795f4SJames Chapman 
31633f72e6fSBill Hong 	ret = l2tp_tunnel_notify(&l2tp_nl_family, info,
31733f72e6fSBill Hong 				 tunnel, L2TP_CMD_TUNNEL_MODIFY);
31833f72e6fSBill Hong 
319309795f4SJames Chapman out:
320309795f4SJames Chapman 	return ret;
321309795f4SJames Chapman }
322309795f4SJames Chapman 
32315e47304SEric W. Biederman static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 portid, u32 seq, int flags,
32433f72e6fSBill Hong 			       struct l2tp_tunnel *tunnel, u8 cmd)
325309795f4SJames Chapman {
326309795f4SJames Chapman 	void *hdr;
327309795f4SJames Chapman 	struct nlattr *nest;
328309795f4SJames Chapman 	struct sock *sk = NULL;
329309795f4SJames Chapman 	struct inet_sock *inet;
330f9bac8dfSChris Elston #if IS_ENABLED(CONFIG_IPV6)
331f9bac8dfSChris Elston 	struct ipv6_pinfo *np = NULL;
332f9bac8dfSChris Elston #endif
333309795f4SJames Chapman 
33433f72e6fSBill Hong 	hdr = genlmsg_put(skb, portid, seq, &l2tp_nl_family, flags, cmd);
3357f8436a1SWei Yongjun 	if (!hdr)
3367f8436a1SWei Yongjun 		return -EMSGSIZE;
337309795f4SJames Chapman 
33860aed2abSDavid S. Miller 	if (nla_put_u8(skb, L2TP_ATTR_PROTO_VERSION, tunnel->version) ||
33960aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id) ||
34060aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id) ||
34160aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_DEBUG, tunnel->debug) ||
34260aed2abSDavid S. Miller 	    nla_put_u16(skb, L2TP_ATTR_ENCAP_TYPE, tunnel->encap))
34360aed2abSDavid S. Miller 		goto nla_put_failure;
344309795f4SJames Chapman 
345309795f4SJames Chapman 	nest = nla_nest_start(skb, L2TP_ATTR_STATS);
346309795f4SJames Chapman 	if (nest == NULL)
347309795f4SJames Chapman 		goto nla_put_failure;
348309795f4SJames Chapman 
3491c714a92SNicolas Dichtel 	if (nla_put_u64_64bit(skb, L2TP_ATTR_TX_PACKETS,
3501c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.tx_packets),
3511c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
3521c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_TX_BYTES,
3531c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.tx_bytes),
3541c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
3551c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_TX_ERRORS,
3561c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.tx_errors),
3571c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
3581c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_PACKETS,
3591c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.rx_packets),
3601c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
3611c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_BYTES,
3621c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.rx_bytes),
3631c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
3641c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_SEQ_DISCARDS,
3651c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.rx_seq_discards),
3661c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
3671c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_OOS_PACKETS,
3681c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.rx_oos_packets),
3691c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
3701c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_ERRORS,
3711c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.rx_errors),
3721c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD))
37360aed2abSDavid S. Miller 		goto nla_put_failure;
374309795f4SJames Chapman 	nla_nest_end(skb, nest);
375309795f4SJames Chapman 
376309795f4SJames Chapman 	sk = tunnel->sock;
377309795f4SJames Chapman 	if (!sk)
378309795f4SJames Chapman 		goto out;
379309795f4SJames Chapman 
380f9bac8dfSChris Elston #if IS_ENABLED(CONFIG_IPV6)
381f9bac8dfSChris Elston 	if (sk->sk_family == AF_INET6)
382f9bac8dfSChris Elston 		np = inet6_sk(sk);
383f9bac8dfSChris Elston #endif
384f9bac8dfSChris Elston 
385309795f4SJames Chapman 	inet = inet_sk(sk);
386309795f4SJames Chapman 
387309795f4SJames Chapman 	switch (tunnel->encap) {
388309795f4SJames Chapman 	case L2TP_ENCAPTYPE_UDP:
38960aed2abSDavid S. Miller 		if (nla_put_u16(skb, L2TP_ATTR_UDP_SPORT, ntohs(inet->inet_sport)) ||
39060aed2abSDavid S. Miller 		    nla_put_u16(skb, L2TP_ATTR_UDP_DPORT, ntohs(inet->inet_dport)) ||
39128448b80STom Herbert 		    nla_put_u8(skb, L2TP_ATTR_UDP_CSUM, !sk->sk_no_check_tx))
39260aed2abSDavid S. Miller 			goto nla_put_failure;
393309795f4SJames Chapman 		/* NOBREAK */
394309795f4SJames Chapman 	case L2TP_ENCAPTYPE_IP:
395f9bac8dfSChris Elston #if IS_ENABLED(CONFIG_IPV6)
396f9bac8dfSChris Elston 		if (np) {
397930345eaSJiri Benc 			if (nla_put_in6_addr(skb, L2TP_ATTR_IP6_SADDR,
398f9bac8dfSChris Elston 					     &np->saddr) ||
399930345eaSJiri Benc 			    nla_put_in6_addr(skb, L2TP_ATTR_IP6_DADDR,
400efe4208fSEric Dumazet 					     &sk->sk_v6_daddr))
401f9bac8dfSChris Elston 				goto nla_put_failure;
402f9bac8dfSChris Elston 		} else
403f9bac8dfSChris Elston #endif
404930345eaSJiri Benc 		if (nla_put_in_addr(skb, L2TP_ATTR_IP_SADDR,
405930345eaSJiri Benc 				    inet->inet_saddr) ||
406930345eaSJiri Benc 		    nla_put_in_addr(skb, L2TP_ATTR_IP_DADDR,
407930345eaSJiri Benc 				    inet->inet_daddr))
40860aed2abSDavid S. Miller 			goto nla_put_failure;
409309795f4SJames Chapman 		break;
410309795f4SJames Chapman 	}
411309795f4SJames Chapman 
412309795f4SJames Chapman out:
413053c095aSJohannes Berg 	genlmsg_end(skb, hdr);
414053c095aSJohannes Berg 	return 0;
415309795f4SJames Chapman 
416309795f4SJames Chapman nla_put_failure:
417309795f4SJames Chapman 	genlmsg_cancel(skb, hdr);
418309795f4SJames Chapman 	return -1;
419309795f4SJames Chapman }
420309795f4SJames Chapman 
421309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_get(struct sk_buff *skb, struct genl_info *info)
422309795f4SJames Chapman {
423309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
424309795f4SJames Chapman 	struct sk_buff *msg;
425309795f4SJames Chapman 	u32 tunnel_id;
426309795f4SJames Chapman 	int ret = -ENOBUFS;
427309795f4SJames Chapman 	struct net *net = genl_info_net(info);
428309795f4SJames Chapman 
429309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_CONN_ID]) {
430309795f4SJames Chapman 		ret = -EINVAL;
431309795f4SJames Chapman 		goto out;
432309795f4SJames Chapman 	}
433309795f4SJames Chapman 
434309795f4SJames Chapman 	tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
435309795f4SJames Chapman 
436309795f4SJames Chapman 	tunnel = l2tp_tunnel_find(net, tunnel_id);
437309795f4SJames Chapman 	if (tunnel == NULL) {
438309795f4SJames Chapman 		ret = -ENODEV;
439309795f4SJames Chapman 		goto out;
440309795f4SJames Chapman 	}
441309795f4SJames Chapman 
44258050fceSThomas Graf 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
443309795f4SJames Chapman 	if (!msg) {
444309795f4SJames Chapman 		ret = -ENOMEM;
445309795f4SJames Chapman 		goto out;
446309795f4SJames Chapman 	}
447309795f4SJames Chapman 
44815e47304SEric W. Biederman 	ret = l2tp_nl_tunnel_send(msg, info->snd_portid, info->snd_seq,
44933f72e6fSBill Hong 				  NLM_F_ACK, tunnel, L2TP_CMD_TUNNEL_GET);
450309795f4SJames Chapman 	if (ret < 0)
451309795f4SJames Chapman 		goto err_out;
452309795f4SJames Chapman 
45315e47304SEric W. Biederman 	return genlmsg_unicast(net, msg, info->snd_portid);
454309795f4SJames Chapman 
455309795f4SJames Chapman err_out:
456309795f4SJames Chapman 	nlmsg_free(msg);
457309795f4SJames Chapman 
458309795f4SJames Chapman out:
459309795f4SJames Chapman 	return ret;
460309795f4SJames Chapman }
461309795f4SJames Chapman 
462309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_dump(struct sk_buff *skb, struct netlink_callback *cb)
463309795f4SJames Chapman {
464309795f4SJames Chapman 	int ti = cb->args[0];
465309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
466309795f4SJames Chapman 	struct net *net = sock_net(skb->sk);
467309795f4SJames Chapman 
468309795f4SJames Chapman 	for (;;) {
469309795f4SJames Chapman 		tunnel = l2tp_tunnel_find_nth(net, ti);
470309795f4SJames Chapman 		if (tunnel == NULL)
471309795f4SJames Chapman 			goto out;
472309795f4SJames Chapman 
47315e47304SEric W. Biederman 		if (l2tp_nl_tunnel_send(skb, NETLINK_CB(cb->skb).portid,
474309795f4SJames Chapman 					cb->nlh->nlmsg_seq, NLM_F_MULTI,
475053c095aSJohannes Berg 					tunnel, L2TP_CMD_TUNNEL_GET) < 0)
476309795f4SJames Chapman 			goto out;
477309795f4SJames Chapman 
478309795f4SJames Chapman 		ti++;
479309795f4SJames Chapman 	}
480309795f4SJames Chapman 
481309795f4SJames Chapman out:
482309795f4SJames Chapman 	cb->args[0] = ti;
483309795f4SJames Chapman 
484309795f4SJames Chapman 	return skb->len;
485309795f4SJames Chapman }
486309795f4SJames Chapman 
487309795f4SJames Chapman static int l2tp_nl_cmd_session_create(struct sk_buff *skb, struct genl_info *info)
488309795f4SJames Chapman {
489309795f4SJames Chapman 	u32 tunnel_id = 0;
490309795f4SJames Chapman 	u32 session_id;
491309795f4SJames Chapman 	u32 peer_session_id;
492309795f4SJames Chapman 	int ret = 0;
493309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
494309795f4SJames Chapman 	struct l2tp_session *session;
495309795f4SJames Chapman 	struct l2tp_session_cfg cfg = { 0, };
496309795f4SJames Chapman 	struct net *net = genl_info_net(info);
497309795f4SJames Chapman 
498309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_CONN_ID]) {
499309795f4SJames Chapman 		ret = -EINVAL;
500309795f4SJames Chapman 		goto out;
501309795f4SJames Chapman 	}
502309795f4SJames Chapman 	tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
503309795f4SJames Chapman 	tunnel = l2tp_tunnel_find(net, tunnel_id);
504309795f4SJames Chapman 	if (!tunnel) {
505309795f4SJames Chapman 		ret = -ENODEV;
506309795f4SJames Chapman 		goto out;
507309795f4SJames Chapman 	}
508309795f4SJames Chapman 
509309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_SESSION_ID]) {
510309795f4SJames Chapman 		ret = -EINVAL;
511309795f4SJames Chapman 		goto out;
512309795f4SJames Chapman 	}
513309795f4SJames Chapman 	session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]);
514309795f4SJames Chapman 	session = l2tp_session_find(net, tunnel, session_id);
515309795f4SJames Chapman 	if (session) {
516309795f4SJames Chapman 		ret = -EEXIST;
517309795f4SJames Chapman 		goto out;
518309795f4SJames Chapman 	}
519309795f4SJames Chapman 
520309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_PEER_SESSION_ID]) {
521309795f4SJames Chapman 		ret = -EINVAL;
522309795f4SJames Chapman 		goto out;
523309795f4SJames Chapman 	}
524309795f4SJames Chapman 	peer_session_id = nla_get_u32(info->attrs[L2TP_ATTR_PEER_SESSION_ID]);
525309795f4SJames Chapman 
526309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_PW_TYPE]) {
527309795f4SJames Chapman 		ret = -EINVAL;
528309795f4SJames Chapman 		goto out;
529309795f4SJames Chapman 	}
530309795f4SJames Chapman 	cfg.pw_type = nla_get_u16(info->attrs[L2TP_ATTR_PW_TYPE]);
531309795f4SJames Chapman 	if (cfg.pw_type >= __L2TP_PWTYPE_MAX) {
532309795f4SJames Chapman 		ret = -EINVAL;
533309795f4SJames Chapman 		goto out;
534309795f4SJames Chapman 	}
535309795f4SJames Chapman 
536309795f4SJames Chapman 	if (tunnel->version > 2) {
537309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_OFFSET])
538309795f4SJames Chapman 			cfg.offset = nla_get_u16(info->attrs[L2TP_ATTR_OFFSET]);
539309795f4SJames Chapman 
540309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_DATA_SEQ])
541309795f4SJames Chapman 			cfg.data_seq = nla_get_u8(info->attrs[L2TP_ATTR_DATA_SEQ]);
542309795f4SJames Chapman 
543309795f4SJames Chapman 		cfg.l2specific_type = L2TP_L2SPECTYPE_DEFAULT;
544309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_L2SPEC_TYPE])
545309795f4SJames Chapman 			cfg.l2specific_type = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_TYPE]);
546309795f4SJames Chapman 
547309795f4SJames Chapman 		cfg.l2specific_len = 4;
548309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_L2SPEC_LEN])
549309795f4SJames Chapman 			cfg.l2specific_len = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_LEN]);
550309795f4SJames Chapman 
551309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_COOKIE]) {
552309795f4SJames Chapman 			u16 len = nla_len(info->attrs[L2TP_ATTR_COOKIE]);
553309795f4SJames Chapman 			if (len > 8) {
554309795f4SJames Chapman 				ret = -EINVAL;
555309795f4SJames Chapman 				goto out;
556309795f4SJames Chapman 			}
557309795f4SJames Chapman 			cfg.cookie_len = len;
558309795f4SJames Chapman 			memcpy(&cfg.cookie[0], nla_data(info->attrs[L2TP_ATTR_COOKIE]), len);
559309795f4SJames Chapman 		}
560309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_PEER_COOKIE]) {
561309795f4SJames Chapman 			u16 len = nla_len(info->attrs[L2TP_ATTR_PEER_COOKIE]);
562309795f4SJames Chapman 			if (len > 8) {
563309795f4SJames Chapman 				ret = -EINVAL;
564309795f4SJames Chapman 				goto out;
565309795f4SJames Chapman 			}
566309795f4SJames Chapman 			cfg.peer_cookie_len = len;
567309795f4SJames Chapman 			memcpy(&cfg.peer_cookie[0], nla_data(info->attrs[L2TP_ATTR_PEER_COOKIE]), len);
568309795f4SJames Chapman 		}
569309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_IFNAME])
570309795f4SJames Chapman 			cfg.ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]);
571309795f4SJames Chapman 
572309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_VLAN_ID])
573309795f4SJames Chapman 			cfg.vlan_id = nla_get_u16(info->attrs[L2TP_ATTR_VLAN_ID]);
574309795f4SJames Chapman 	}
575309795f4SJames Chapman 
576309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_DEBUG])
577309795f4SJames Chapman 		cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
578309795f4SJames Chapman 
579309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_RECV_SEQ])
580309795f4SJames Chapman 		cfg.recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]);
581309795f4SJames Chapman 
582309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_SEND_SEQ])
583309795f4SJames Chapman 		cfg.send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]);
584309795f4SJames Chapman 
585309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_LNS_MODE])
586309795f4SJames Chapman 		cfg.lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]);
587309795f4SJames Chapman 
588309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_RECV_TIMEOUT])
589309795f4SJames Chapman 		cfg.reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]);
590309795f4SJames Chapman 
591309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_MTU])
592309795f4SJames Chapman 		cfg.mtu = nla_get_u16(info->attrs[L2TP_ATTR_MTU]);
593309795f4SJames Chapman 
594309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_MRU])
595309795f4SJames Chapman 		cfg.mru = nla_get_u16(info->attrs[L2TP_ATTR_MRU]);
596309795f4SJames Chapman 
597f1f39f91Sstephen hemminger #ifdef CONFIG_MODULES
598f1f39f91Sstephen hemminger 	if (l2tp_nl_cmd_ops[cfg.pw_type] == NULL) {
599f1f39f91Sstephen hemminger 		genl_unlock();
600f1f39f91Sstephen hemminger 		request_module("net-l2tp-type-%u", cfg.pw_type);
601f1f39f91Sstephen hemminger 		genl_lock();
602f1f39f91Sstephen hemminger 	}
603f1f39f91Sstephen hemminger #endif
604309795f4SJames Chapman 	if ((l2tp_nl_cmd_ops[cfg.pw_type] == NULL) ||
605309795f4SJames Chapman 	    (l2tp_nl_cmd_ops[cfg.pw_type]->session_create == NULL)) {
606309795f4SJames Chapman 		ret = -EPROTONOSUPPORT;
607309795f4SJames Chapman 		goto out;
608309795f4SJames Chapman 	}
609309795f4SJames Chapman 
610309795f4SJames Chapman 	/* Check that pseudowire-specific params are present */
611309795f4SJames Chapman 	switch (cfg.pw_type) {
612309795f4SJames Chapman 	case L2TP_PWTYPE_NONE:
613309795f4SJames Chapman 		break;
614309795f4SJames Chapman 	case L2TP_PWTYPE_ETH_VLAN:
615309795f4SJames Chapman 		if (!info->attrs[L2TP_ATTR_VLAN_ID]) {
616309795f4SJames Chapman 			ret = -EINVAL;
617309795f4SJames Chapman 			goto out;
618309795f4SJames Chapman 		}
619309795f4SJames Chapman 		break;
620309795f4SJames Chapman 	case L2TP_PWTYPE_ETH:
621309795f4SJames Chapman 		break;
622309795f4SJames Chapman 	case L2TP_PWTYPE_PPP:
623309795f4SJames Chapman 	case L2TP_PWTYPE_PPP_AC:
624309795f4SJames Chapman 		break;
625309795f4SJames Chapman 	case L2TP_PWTYPE_IP:
626309795f4SJames Chapman 	default:
627309795f4SJames Chapman 		ret = -EPROTONOSUPPORT;
628309795f4SJames Chapman 		break;
629309795f4SJames Chapman 	}
630309795f4SJames Chapman 
631309795f4SJames Chapman 	ret = -EPROTONOSUPPORT;
632309795f4SJames Chapman 	if (l2tp_nl_cmd_ops[cfg.pw_type]->session_create)
633309795f4SJames Chapman 		ret = (*l2tp_nl_cmd_ops[cfg.pw_type]->session_create)(net, tunnel_id,
634309795f4SJames Chapman 			session_id, peer_session_id, &cfg);
635309795f4SJames Chapman 
63633f72e6fSBill Hong 	if (ret >= 0) {
63733f72e6fSBill Hong 		session = l2tp_session_find(net, tunnel, session_id);
63833f72e6fSBill Hong 		if (session)
63933f72e6fSBill Hong 			ret = l2tp_session_notify(&l2tp_nl_family, info, session,
64033f72e6fSBill Hong 						  L2TP_CMD_SESSION_CREATE);
64133f72e6fSBill Hong 	}
64233f72e6fSBill Hong 
643309795f4SJames Chapman out:
644309795f4SJames Chapman 	return ret;
645309795f4SJames Chapman }
646309795f4SJames Chapman 
647309795f4SJames Chapman static int l2tp_nl_cmd_session_delete(struct sk_buff *skb, struct genl_info *info)
648309795f4SJames Chapman {
649309795f4SJames Chapman 	int ret = 0;
650309795f4SJames Chapman 	struct l2tp_session *session;
651309795f4SJames Chapman 	u16 pw_type;
652309795f4SJames Chapman 
653309795f4SJames Chapman 	session = l2tp_nl_session_find(info);
654309795f4SJames Chapman 	if (session == NULL) {
655309795f4SJames Chapman 		ret = -ENODEV;
656309795f4SJames Chapman 		goto out;
657309795f4SJames Chapman 	}
658309795f4SJames Chapman 
65933f72e6fSBill Hong 	l2tp_session_notify(&l2tp_nl_family, info,
66033f72e6fSBill Hong 			    session, L2TP_CMD_SESSION_DELETE);
66133f72e6fSBill Hong 
662309795f4SJames Chapman 	pw_type = session->pwtype;
663309795f4SJames Chapman 	if (pw_type < __L2TP_PWTYPE_MAX)
664309795f4SJames Chapman 		if (l2tp_nl_cmd_ops[pw_type] && l2tp_nl_cmd_ops[pw_type]->session_delete)
665309795f4SJames Chapman 			ret = (*l2tp_nl_cmd_ops[pw_type]->session_delete)(session);
666309795f4SJames Chapman 
667309795f4SJames Chapman out:
668309795f4SJames Chapman 	return ret;
669309795f4SJames Chapman }
670309795f4SJames Chapman 
671309795f4SJames Chapman static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *info)
672309795f4SJames Chapman {
673309795f4SJames Chapman 	int ret = 0;
674309795f4SJames Chapman 	struct l2tp_session *session;
675309795f4SJames Chapman 
676309795f4SJames Chapman 	session = l2tp_nl_session_find(info);
677309795f4SJames Chapman 	if (session == NULL) {
678309795f4SJames Chapman 		ret = -ENODEV;
679309795f4SJames Chapman 		goto out;
680309795f4SJames Chapman 	}
681309795f4SJames Chapman 
682309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_DEBUG])
683309795f4SJames Chapman 		session->debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
684309795f4SJames Chapman 
685309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_DATA_SEQ])
686309795f4SJames Chapman 		session->data_seq = nla_get_u8(info->attrs[L2TP_ATTR_DATA_SEQ]);
687309795f4SJames Chapman 
688309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_RECV_SEQ])
689309795f4SJames Chapman 		session->recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]);
690309795f4SJames Chapman 
691bb5016eaSGuillaume Nault 	if (info->attrs[L2TP_ATTR_SEND_SEQ]) {
692309795f4SJames Chapman 		session->send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]);
693bb5016eaSGuillaume Nault 		l2tp_session_set_header_len(session, session->tunnel->version);
694bb5016eaSGuillaume Nault 	}
695309795f4SJames Chapman 
696309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_LNS_MODE])
697309795f4SJames Chapman 		session->lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]);
698309795f4SJames Chapman 
699309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_RECV_TIMEOUT])
700309795f4SJames Chapman 		session->reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]);
701309795f4SJames Chapman 
702309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_MTU])
703309795f4SJames Chapman 		session->mtu = nla_get_u16(info->attrs[L2TP_ATTR_MTU]);
704309795f4SJames Chapman 
705309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_MRU])
706309795f4SJames Chapman 		session->mru = nla_get_u16(info->attrs[L2TP_ATTR_MRU]);
707309795f4SJames Chapman 
70833f72e6fSBill Hong 	ret = l2tp_session_notify(&l2tp_nl_family, info,
70933f72e6fSBill Hong 				  session, L2TP_CMD_SESSION_MODIFY);
71033f72e6fSBill Hong 
711309795f4SJames Chapman out:
712309795f4SJames Chapman 	return ret;
713309795f4SJames Chapman }
714309795f4SJames Chapman 
71515e47304SEric W. Biederman static int l2tp_nl_session_send(struct sk_buff *skb, u32 portid, u32 seq, int flags,
71633f72e6fSBill Hong 				struct l2tp_session *session, u8 cmd)
717309795f4SJames Chapman {
718309795f4SJames Chapman 	void *hdr;
719309795f4SJames Chapman 	struct nlattr *nest;
720309795f4SJames Chapman 	struct l2tp_tunnel *tunnel = session->tunnel;
721309795f4SJames Chapman 	struct sock *sk = NULL;
722309795f4SJames Chapman 
723309795f4SJames Chapman 	sk = tunnel->sock;
724309795f4SJames Chapman 
72533f72e6fSBill Hong 	hdr = genlmsg_put(skb, portid, seq, &l2tp_nl_family, flags, cmd);
7267f8436a1SWei Yongjun 	if (!hdr)
7277f8436a1SWei Yongjun 		return -EMSGSIZE;
728309795f4SJames Chapman 
72960aed2abSDavid S. Miller 	if (nla_put_u32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id) ||
73060aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_SESSION_ID, session->session_id) ||
73160aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id) ||
73260aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_PEER_SESSION_ID,
73360aed2abSDavid S. Miller 			session->peer_session_id) ||
73460aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_DEBUG, session->debug) ||
73560aed2abSDavid S. Miller 	    nla_put_u16(skb, L2TP_ATTR_PW_TYPE, session->pwtype) ||
73660aed2abSDavid S. Miller 	    nla_put_u16(skb, L2TP_ATTR_MTU, session->mtu) ||
73760aed2abSDavid S. Miller 	    (session->mru &&
73860aed2abSDavid S. Miller 	     nla_put_u16(skb, L2TP_ATTR_MRU, session->mru)))
73960aed2abSDavid S. Miller 		goto nla_put_failure;
740309795f4SJames Chapman 
741e269ed26SAlan Cox 	if ((session->ifname[0] &&
74260aed2abSDavid S. Miller 	     nla_put_string(skb, L2TP_ATTR_IFNAME, session->ifname)) ||
74360aed2abSDavid S. Miller 	    (session->cookie_len &&
74460aed2abSDavid S. Miller 	     nla_put(skb, L2TP_ATTR_COOKIE, session->cookie_len,
74560aed2abSDavid S. Miller 		     &session->cookie[0])) ||
74660aed2abSDavid S. Miller 	    (session->peer_cookie_len &&
74760aed2abSDavid S. Miller 	     nla_put(skb, L2TP_ATTR_PEER_COOKIE, session->peer_cookie_len,
74860aed2abSDavid S. Miller 		     &session->peer_cookie[0])) ||
74960aed2abSDavid S. Miller 	    nla_put_u8(skb, L2TP_ATTR_RECV_SEQ, session->recv_seq) ||
75060aed2abSDavid S. Miller 	    nla_put_u8(skb, L2TP_ATTR_SEND_SEQ, session->send_seq) ||
75160aed2abSDavid S. Miller 	    nla_put_u8(skb, L2TP_ATTR_LNS_MODE, session->lns_mode) ||
752309795f4SJames Chapman #ifdef CONFIG_XFRM
75360aed2abSDavid S. Miller 	    (((sk) && (sk->sk_policy[0] || sk->sk_policy[1])) &&
75460aed2abSDavid S. Miller 	     nla_put_u8(skb, L2TP_ATTR_USING_IPSEC, 1)) ||
755309795f4SJames Chapman #endif
75660aed2abSDavid S. Miller 	    (session->reorder_timeout &&
7572175d87cSNicolas Dichtel 	     nla_put_msecs(skb, L2TP_ATTR_RECV_TIMEOUT,
7582175d87cSNicolas Dichtel 			   session->reorder_timeout, L2TP_ATTR_PAD)))
75960aed2abSDavid S. Miller 		goto nla_put_failure;
7605de7aee5SJames Chapman 
761309795f4SJames Chapman 	nest = nla_nest_start(skb, L2TP_ATTR_STATS);
762309795f4SJames Chapman 	if (nest == NULL)
763309795f4SJames Chapman 		goto nla_put_failure;
7645de7aee5SJames Chapman 
7651c714a92SNicolas Dichtel 	if (nla_put_u64_64bit(skb, L2TP_ATTR_TX_PACKETS,
7661c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.tx_packets),
7671c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7681c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_TX_BYTES,
7691c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.tx_bytes),
7701c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7711c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_TX_ERRORS,
7721c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.tx_errors),
7731c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7741c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_PACKETS,
7751c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.rx_packets),
7761c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7771c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_BYTES,
7781c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.rx_bytes),
7791c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7801c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_SEQ_DISCARDS,
7811c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.rx_seq_discards),
7821c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7831c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_OOS_PACKETS,
7841c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.rx_oos_packets),
7851c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7861c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_ERRORS,
7871c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.rx_errors),
7881c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD))
78960aed2abSDavid S. Miller 		goto nla_put_failure;
790309795f4SJames Chapman 	nla_nest_end(skb, nest);
791309795f4SJames Chapman 
792053c095aSJohannes Berg 	genlmsg_end(skb, hdr);
793053c095aSJohannes Berg 	return 0;
794309795f4SJames Chapman 
795309795f4SJames Chapman  nla_put_failure:
796309795f4SJames Chapman 	genlmsg_cancel(skb, hdr);
797309795f4SJames Chapman 	return -1;
798309795f4SJames Chapman }
799309795f4SJames Chapman 
800309795f4SJames Chapman static int l2tp_nl_cmd_session_get(struct sk_buff *skb, struct genl_info *info)
801309795f4SJames Chapman {
802309795f4SJames Chapman 	struct l2tp_session *session;
803309795f4SJames Chapman 	struct sk_buff *msg;
804309795f4SJames Chapman 	int ret;
805309795f4SJames Chapman 
806309795f4SJames Chapman 	session = l2tp_nl_session_find(info);
807309795f4SJames Chapman 	if (session == NULL) {
808309795f4SJames Chapman 		ret = -ENODEV;
809309795f4SJames Chapman 		goto out;
810309795f4SJames Chapman 	}
811309795f4SJames Chapman 
81258050fceSThomas Graf 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
813309795f4SJames Chapman 	if (!msg) {
814309795f4SJames Chapman 		ret = -ENOMEM;
815309795f4SJames Chapman 		goto out;
816309795f4SJames Chapman 	}
817309795f4SJames Chapman 
81815e47304SEric W. Biederman 	ret = l2tp_nl_session_send(msg, info->snd_portid, info->snd_seq,
81933f72e6fSBill Hong 				   0, session, L2TP_CMD_SESSION_GET);
820309795f4SJames Chapman 	if (ret < 0)
821309795f4SJames Chapman 		goto err_out;
822309795f4SJames Chapman 
82315e47304SEric W. Biederman 	return genlmsg_unicast(genl_info_net(info), msg, info->snd_portid);
824309795f4SJames Chapman 
825309795f4SJames Chapman err_out:
826309795f4SJames Chapman 	nlmsg_free(msg);
827309795f4SJames Chapman 
828309795f4SJames Chapman out:
829309795f4SJames Chapman 	return ret;
830309795f4SJames Chapman }
831309795f4SJames Chapman 
832309795f4SJames Chapman static int l2tp_nl_cmd_session_dump(struct sk_buff *skb, struct netlink_callback *cb)
833309795f4SJames Chapman {
834309795f4SJames Chapman 	struct net *net = sock_net(skb->sk);
835309795f4SJames Chapman 	struct l2tp_session *session;
836309795f4SJames Chapman 	struct l2tp_tunnel *tunnel = NULL;
837309795f4SJames Chapman 	int ti = cb->args[0];
838309795f4SJames Chapman 	int si = cb->args[1];
839309795f4SJames Chapman 
840309795f4SJames Chapman 	for (;;) {
841309795f4SJames Chapman 		if (tunnel == NULL) {
842309795f4SJames Chapman 			tunnel = l2tp_tunnel_find_nth(net, ti);
843309795f4SJames Chapman 			if (tunnel == NULL)
844309795f4SJames Chapman 				goto out;
845309795f4SJames Chapman 		}
846309795f4SJames Chapman 
847309795f4SJames Chapman 		session = l2tp_session_find_nth(tunnel, si);
848309795f4SJames Chapman 		if (session == NULL) {
849309795f4SJames Chapman 			ti++;
850309795f4SJames Chapman 			tunnel = NULL;
851309795f4SJames Chapman 			si = 0;
852309795f4SJames Chapman 			continue;
853309795f4SJames Chapman 		}
854309795f4SJames Chapman 
85515e47304SEric W. Biederman 		if (l2tp_nl_session_send(skb, NETLINK_CB(cb->skb).portid,
856309795f4SJames Chapman 					 cb->nlh->nlmsg_seq, NLM_F_MULTI,
857053c095aSJohannes Berg 					 session, L2TP_CMD_SESSION_GET) < 0)
858309795f4SJames Chapman 			break;
859309795f4SJames Chapman 
860309795f4SJames Chapman 		si++;
861309795f4SJames Chapman 	}
862309795f4SJames Chapman 
863309795f4SJames Chapman out:
864309795f4SJames Chapman 	cb->args[0] = ti;
865309795f4SJames Chapman 	cb->args[1] = si;
866309795f4SJames Chapman 
867309795f4SJames Chapman 	return skb->len;
868309795f4SJames Chapman }
869309795f4SJames Chapman 
870*f5bb341eSstephen hemminger static const struct nla_policy l2tp_nl_policy[L2TP_ATTR_MAX + 1] = {
871309795f4SJames Chapman 	[L2TP_ATTR_NONE]		= { .type = NLA_UNSPEC, },
872309795f4SJames Chapman 	[L2TP_ATTR_PW_TYPE]		= { .type = NLA_U16, },
873309795f4SJames Chapman 	[L2TP_ATTR_ENCAP_TYPE]		= { .type = NLA_U16, },
874309795f4SJames Chapman 	[L2TP_ATTR_OFFSET]		= { .type = NLA_U16, },
875309795f4SJames Chapman 	[L2TP_ATTR_DATA_SEQ]		= { .type = NLA_U8, },
876309795f4SJames Chapman 	[L2TP_ATTR_L2SPEC_TYPE]		= { .type = NLA_U8, },
877309795f4SJames Chapman 	[L2TP_ATTR_L2SPEC_LEN]		= { .type = NLA_U8, },
878309795f4SJames Chapman 	[L2TP_ATTR_PROTO_VERSION]	= { .type = NLA_U8, },
879309795f4SJames Chapman 	[L2TP_ATTR_CONN_ID]		= { .type = NLA_U32, },
880309795f4SJames Chapman 	[L2TP_ATTR_PEER_CONN_ID]	= { .type = NLA_U32, },
881309795f4SJames Chapman 	[L2TP_ATTR_SESSION_ID]		= { .type = NLA_U32, },
882309795f4SJames Chapman 	[L2TP_ATTR_PEER_SESSION_ID]	= { .type = NLA_U32, },
883309795f4SJames Chapman 	[L2TP_ATTR_UDP_CSUM]		= { .type = NLA_U8, },
884309795f4SJames Chapman 	[L2TP_ATTR_VLAN_ID]		= { .type = NLA_U16, },
885309795f4SJames Chapman 	[L2TP_ATTR_DEBUG]		= { .type = NLA_U32, },
886309795f4SJames Chapman 	[L2TP_ATTR_RECV_SEQ]		= { .type = NLA_U8, },
887309795f4SJames Chapman 	[L2TP_ATTR_SEND_SEQ]		= { .type = NLA_U8, },
888309795f4SJames Chapman 	[L2TP_ATTR_LNS_MODE]		= { .type = NLA_U8, },
889309795f4SJames Chapman 	[L2TP_ATTR_USING_IPSEC]		= { .type = NLA_U8, },
890309795f4SJames Chapman 	[L2TP_ATTR_RECV_TIMEOUT]	= { .type = NLA_MSECS, },
891309795f4SJames Chapman 	[L2TP_ATTR_FD]			= { .type = NLA_U32, },
892309795f4SJames Chapman 	[L2TP_ATTR_IP_SADDR]		= { .type = NLA_U32, },
893309795f4SJames Chapman 	[L2TP_ATTR_IP_DADDR]		= { .type = NLA_U32, },
894309795f4SJames Chapman 	[L2TP_ATTR_UDP_SPORT]		= { .type = NLA_U16, },
895309795f4SJames Chapman 	[L2TP_ATTR_UDP_DPORT]		= { .type = NLA_U16, },
896309795f4SJames Chapman 	[L2TP_ATTR_MTU]			= { .type = NLA_U16, },
897309795f4SJames Chapman 	[L2TP_ATTR_MRU]			= { .type = NLA_U16, },
898309795f4SJames Chapman 	[L2TP_ATTR_STATS]		= { .type = NLA_NESTED, },
899f9bac8dfSChris Elston 	[L2TP_ATTR_IP6_SADDR] = {
900f9bac8dfSChris Elston 		.type = NLA_BINARY,
901f9bac8dfSChris Elston 		.len = sizeof(struct in6_addr),
902f9bac8dfSChris Elston 	},
903f9bac8dfSChris Elston 	[L2TP_ATTR_IP6_DADDR] = {
904f9bac8dfSChris Elston 		.type = NLA_BINARY,
905f9bac8dfSChris Elston 		.len = sizeof(struct in6_addr),
906f9bac8dfSChris Elston 	},
907309795f4SJames Chapman 	[L2TP_ATTR_IFNAME] = {
908309795f4SJames Chapman 		.type = NLA_NUL_STRING,
909309795f4SJames Chapman 		.len = IFNAMSIZ - 1,
910309795f4SJames Chapman 	},
911309795f4SJames Chapman 	[L2TP_ATTR_COOKIE] = {
912309795f4SJames Chapman 		.type = NLA_BINARY,
913309795f4SJames Chapman 		.len = 8,
914309795f4SJames Chapman 	},
915309795f4SJames Chapman 	[L2TP_ATTR_PEER_COOKIE] = {
916309795f4SJames Chapman 		.type = NLA_BINARY,
917309795f4SJames Chapman 		.len = 8,
918309795f4SJames Chapman 	},
919309795f4SJames Chapman };
920309795f4SJames Chapman 
9214534de83SJohannes Berg static const struct genl_ops l2tp_nl_ops[] = {
922309795f4SJames Chapman 	{
923309795f4SJames Chapman 		.cmd = L2TP_CMD_NOOP,
924309795f4SJames Chapman 		.doit = l2tp_nl_cmd_noop,
925309795f4SJames Chapman 		.policy = l2tp_nl_policy,
926309795f4SJames Chapman 		/* can be retrieved by unprivileged users */
927309795f4SJames Chapman 	},
928309795f4SJames Chapman 	{
929309795f4SJames Chapman 		.cmd = L2TP_CMD_TUNNEL_CREATE,
930309795f4SJames Chapman 		.doit = l2tp_nl_cmd_tunnel_create,
931309795f4SJames Chapman 		.policy = l2tp_nl_policy,
932309795f4SJames Chapman 		.flags = GENL_ADMIN_PERM,
933309795f4SJames Chapman 	},
934309795f4SJames Chapman 	{
935309795f4SJames Chapman 		.cmd = L2TP_CMD_TUNNEL_DELETE,
936309795f4SJames Chapman 		.doit = l2tp_nl_cmd_tunnel_delete,
937309795f4SJames Chapman 		.policy = l2tp_nl_policy,
938309795f4SJames Chapman 		.flags = GENL_ADMIN_PERM,
939309795f4SJames Chapman 	},
940309795f4SJames Chapman 	{
941309795f4SJames Chapman 		.cmd = L2TP_CMD_TUNNEL_MODIFY,
942309795f4SJames Chapman 		.doit = l2tp_nl_cmd_tunnel_modify,
943309795f4SJames Chapman 		.policy = l2tp_nl_policy,
944309795f4SJames Chapman 		.flags = GENL_ADMIN_PERM,
945309795f4SJames Chapman 	},
946309795f4SJames Chapman 	{
947309795f4SJames Chapman 		.cmd = L2TP_CMD_TUNNEL_GET,
948309795f4SJames Chapman 		.doit = l2tp_nl_cmd_tunnel_get,
949309795f4SJames Chapman 		.dumpit = l2tp_nl_cmd_tunnel_dump,
950309795f4SJames Chapman 		.policy = l2tp_nl_policy,
951309795f4SJames Chapman 		.flags = GENL_ADMIN_PERM,
952309795f4SJames Chapman 	},
953309795f4SJames Chapman 	{
954309795f4SJames Chapman 		.cmd = L2TP_CMD_SESSION_CREATE,
955309795f4SJames Chapman 		.doit = l2tp_nl_cmd_session_create,
956309795f4SJames Chapman 		.policy = l2tp_nl_policy,
957309795f4SJames Chapman 		.flags = GENL_ADMIN_PERM,
958309795f4SJames Chapman 	},
959309795f4SJames Chapman 	{
960309795f4SJames Chapman 		.cmd = L2TP_CMD_SESSION_DELETE,
961309795f4SJames Chapman 		.doit = l2tp_nl_cmd_session_delete,
962309795f4SJames Chapman 		.policy = l2tp_nl_policy,
963309795f4SJames Chapman 		.flags = GENL_ADMIN_PERM,
964309795f4SJames Chapman 	},
965309795f4SJames Chapman 	{
966309795f4SJames Chapman 		.cmd = L2TP_CMD_SESSION_MODIFY,
967309795f4SJames Chapman 		.doit = l2tp_nl_cmd_session_modify,
968309795f4SJames Chapman 		.policy = l2tp_nl_policy,
969309795f4SJames Chapman 		.flags = GENL_ADMIN_PERM,
970309795f4SJames Chapman 	},
971309795f4SJames Chapman 	{
972309795f4SJames Chapman 		.cmd = L2TP_CMD_SESSION_GET,
973309795f4SJames Chapman 		.doit = l2tp_nl_cmd_session_get,
974309795f4SJames Chapman 		.dumpit = l2tp_nl_cmd_session_dump,
975309795f4SJames Chapman 		.policy = l2tp_nl_policy,
976309795f4SJames Chapman 		.flags = GENL_ADMIN_PERM,
977309795f4SJames Chapman 	},
978309795f4SJames Chapman };
979309795f4SJames Chapman 
980309795f4SJames Chapman int l2tp_nl_register_ops(enum l2tp_pwtype pw_type, const struct l2tp_nl_cmd_ops *ops)
981309795f4SJames Chapman {
982309795f4SJames Chapman 	int ret;
983309795f4SJames Chapman 
984309795f4SJames Chapman 	ret = -EINVAL;
985309795f4SJames Chapman 	if (pw_type >= __L2TP_PWTYPE_MAX)
986309795f4SJames Chapman 		goto err;
987309795f4SJames Chapman 
988309795f4SJames Chapman 	genl_lock();
989309795f4SJames Chapman 	ret = -EBUSY;
990309795f4SJames Chapman 	if (l2tp_nl_cmd_ops[pw_type])
991309795f4SJames Chapman 		goto out;
992309795f4SJames Chapman 
993309795f4SJames Chapman 	l2tp_nl_cmd_ops[pw_type] = ops;
9948cb49014SDavid S. Miller 	ret = 0;
995309795f4SJames Chapman 
996309795f4SJames Chapman out:
997309795f4SJames Chapman 	genl_unlock();
998309795f4SJames Chapman err:
9998cb49014SDavid S. Miller 	return ret;
1000309795f4SJames Chapman }
1001309795f4SJames Chapman EXPORT_SYMBOL_GPL(l2tp_nl_register_ops);
1002309795f4SJames Chapman 
1003309795f4SJames Chapman void l2tp_nl_unregister_ops(enum l2tp_pwtype pw_type)
1004309795f4SJames Chapman {
1005309795f4SJames Chapman 	if (pw_type < __L2TP_PWTYPE_MAX) {
1006309795f4SJames Chapman 		genl_lock();
1007309795f4SJames Chapman 		l2tp_nl_cmd_ops[pw_type] = NULL;
1008309795f4SJames Chapman 		genl_unlock();
1009309795f4SJames Chapman 	}
1010309795f4SJames Chapman }
1011309795f4SJames Chapman EXPORT_SYMBOL_GPL(l2tp_nl_unregister_ops);
1012309795f4SJames Chapman 
1013309795f4SJames Chapman static int l2tp_nl_init(void)
1014309795f4SJames Chapman {
1015a4ca44faSJoe Perches 	pr_info("L2TP netlink interface\n");
101633f72e6fSBill Hong 	return genl_register_family_with_ops_groups(&l2tp_nl_family,
101733f72e6fSBill Hong 						    l2tp_nl_ops,
101833f72e6fSBill Hong 						    l2tp_multicast_group);
1019309795f4SJames Chapman }
1020309795f4SJames Chapman 
1021309795f4SJames Chapman static void l2tp_nl_cleanup(void)
1022309795f4SJames Chapman {
1023309795f4SJames Chapman 	genl_unregister_family(&l2tp_nl_family);
1024309795f4SJames Chapman }
1025309795f4SJames Chapman 
1026309795f4SJames Chapman module_init(l2tp_nl_init);
1027309795f4SJames Chapman module_exit(l2tp_nl_cleanup);
1028309795f4SJames Chapman 
1029309795f4SJames Chapman MODULE_AUTHOR("James Chapman <jchapman@katalix.com>");
1030309795f4SJames Chapman MODULE_DESCRIPTION("L2TP netlink");
1031309795f4SJames Chapman MODULE_LICENSE("GPL");
1032309795f4SJames Chapman MODULE_VERSION("1.0");
1033e9412c37SNeil Horman MODULE_ALIAS_GENL_FAMILY("l2tp");
1034