xref: /linux/net/l2tp/l2tp_netlink.c (revision c771600c6af14749609b49565ffb4cac2959710d)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
220dcb110STom Parkin /* 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 
13a4ca44faSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
14a4ca44faSJoe Perches 
15309795f4SJames Chapman #include <net/sock.h>
16309795f4SJames Chapman #include <net/genetlink.h>
17309795f4SJames Chapman #include <net/udp.h>
18309795f4SJames Chapman #include <linux/in.h>
19309795f4SJames Chapman #include <linux/udp.h>
20309795f4SJames Chapman #include <linux/socket.h>
21309795f4SJames Chapman #include <linux/module.h>
22309795f4SJames Chapman #include <linux/list.h>
23309795f4SJames Chapman #include <net/net_namespace.h>
24309795f4SJames Chapman 
25309795f4SJames Chapman #include <linux/l2tp.h>
26309795f4SJames Chapman 
27309795f4SJames Chapman #include "l2tp_core.h"
28309795f4SJames Chapman 
29489111e5SJohannes Berg static struct genl_family l2tp_nl_family;
30309795f4SJames Chapman 
3133f72e6fSBill Hong static const struct genl_multicast_group l2tp_multicast_group[] = {
3233f72e6fSBill Hong 	{
3333f72e6fSBill Hong 		.name = L2TP_GENL_MCGROUP,
3433f72e6fSBill Hong 	},
3533f72e6fSBill Hong };
3633f72e6fSBill Hong 
3733f72e6fSBill Hong static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 portid, u32 seq,
3833f72e6fSBill Hong 			       int flags, struct l2tp_tunnel *tunnel, u8 cmd);
3933f72e6fSBill Hong static int l2tp_nl_session_send(struct sk_buff *skb, u32 portid, u32 seq,
4033f72e6fSBill Hong 				int flags, struct l2tp_session *session,
4133f72e6fSBill Hong 				u8 cmd);
4233f72e6fSBill Hong 
43309795f4SJames Chapman /* Accessed under genl lock */
44309795f4SJames Chapman static const struct l2tp_nl_cmd_ops *l2tp_nl_cmd_ops[__L2TP_PWTYPE_MAX];
45309795f4SJames Chapman 
l2tp_nl_session_get(struct genl_info * info)46a4346210SGuillaume Nault static struct l2tp_session *l2tp_nl_session_get(struct genl_info *info)
47309795f4SJames Chapman {
48309795f4SJames Chapman 	u32 tunnel_id;
49309795f4SJames Chapman 	u32 session_id;
50309795f4SJames Chapman 	char *ifname;
51309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
52309795f4SJames Chapman 	struct l2tp_session *session = NULL;
53309795f4SJames Chapman 	struct net *net = genl_info_net(info);
54309795f4SJames Chapman 
55309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_IFNAME]) {
56309795f4SJames Chapman 		ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]);
57a4346210SGuillaume Nault 		session = l2tp_session_get_by_ifname(net, ifname);
58309795f4SJames Chapman 	} else if ((info->attrs[L2TP_ATTR_SESSION_ID]) &&
59309795f4SJames Chapman 		   (info->attrs[L2TP_ATTR_CONN_ID])) {
60309795f4SJames Chapman 		tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
61309795f4SJames Chapman 		session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]);
6254652eb1SGuillaume Nault 		tunnel = l2tp_tunnel_get(net, tunnel_id);
6354652eb1SGuillaume Nault 		if (tunnel) {
645f77c18eSJames Chapman 			session = l2tp_session_get(net, tunnel->sock, tunnel->version,
655f77c18eSJames Chapman 						   tunnel_id, session_id);
66abe7a1a7SJames Chapman 			l2tp_tunnel_put(tunnel);
6754652eb1SGuillaume Nault 		}
68309795f4SJames Chapman 	}
69309795f4SJames Chapman 
70309795f4SJames Chapman 	return session;
71309795f4SJames Chapman }
72309795f4SJames Chapman 
l2tp_nl_cmd_noop(struct sk_buff * skb,struct genl_info * info)73309795f4SJames Chapman static int l2tp_nl_cmd_noop(struct sk_buff *skb, struct genl_info *info)
74309795f4SJames Chapman {
75309795f4SJames Chapman 	struct sk_buff *msg;
76309795f4SJames Chapman 	void *hdr;
77309795f4SJames Chapman 	int ret = -ENOBUFS;
78309795f4SJames Chapman 
7958050fceSThomas Graf 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
80309795f4SJames Chapman 	if (!msg) {
81309795f4SJames Chapman 		ret = -ENOMEM;
82309795f4SJames Chapman 		goto out;
83309795f4SJames Chapman 	}
84309795f4SJames Chapman 
8515e47304SEric W. Biederman 	hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
86309795f4SJames Chapman 			  &l2tp_nl_family, 0, L2TP_CMD_NOOP);
877f8436a1SWei Yongjun 	if (!hdr) {
887f8436a1SWei Yongjun 		ret = -EMSGSIZE;
89309795f4SJames Chapman 		goto err_out;
90309795f4SJames Chapman 	}
91309795f4SJames Chapman 
92309795f4SJames Chapman 	genlmsg_end(msg, hdr);
93309795f4SJames Chapman 
9415e47304SEric W. Biederman 	return genlmsg_unicast(genl_info_net(info), msg, info->snd_portid);
95309795f4SJames Chapman 
96309795f4SJames Chapman err_out:
97309795f4SJames Chapman 	nlmsg_free(msg);
98309795f4SJames Chapman 
99309795f4SJames Chapman out:
100309795f4SJames Chapman 	return ret;
101309795f4SJames Chapman }
102309795f4SJames Chapman 
l2tp_tunnel_notify(struct genl_family * family,struct genl_info * info,struct l2tp_tunnel * tunnel,u8 cmd)10333f72e6fSBill Hong static int l2tp_tunnel_notify(struct genl_family *family,
10433f72e6fSBill Hong 			      struct genl_info *info,
10533f72e6fSBill Hong 			      struct l2tp_tunnel *tunnel,
10633f72e6fSBill Hong 			      u8 cmd)
10733f72e6fSBill Hong {
10833f72e6fSBill Hong 	struct sk_buff *msg;
10933f72e6fSBill Hong 	int ret;
11033f72e6fSBill Hong 
11133f72e6fSBill Hong 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
11233f72e6fSBill Hong 	if (!msg)
11333f72e6fSBill Hong 		return -ENOMEM;
11433f72e6fSBill Hong 
11533f72e6fSBill Hong 	ret = l2tp_nl_tunnel_send(msg, info->snd_portid, info->snd_seq,
11633f72e6fSBill Hong 				  NLM_F_ACK, tunnel, cmd);
11733f72e6fSBill Hong 
118853effc5SMark Tomlinson 	if (ret >= 0) {
11956440d7eSEric Dumazet 		ret = genlmsg_multicast_allns(family, msg, 0, 0);
120853effc5SMark Tomlinson 		/* We don't care if no one is listening */
121853effc5SMark Tomlinson 		if (ret == -ESRCH)
122853effc5SMark Tomlinson 			ret = 0;
123853effc5SMark Tomlinson 		return ret;
124853effc5SMark Tomlinson 	}
12533f72e6fSBill Hong 
12633f72e6fSBill Hong 	nlmsg_free(msg);
12733f72e6fSBill Hong 
12833f72e6fSBill Hong 	return ret;
12933f72e6fSBill Hong }
13033f72e6fSBill Hong 
l2tp_session_notify(struct genl_family * family,struct genl_info * info,struct l2tp_session * session,u8 cmd)13133f72e6fSBill Hong static int l2tp_session_notify(struct genl_family *family,
13233f72e6fSBill Hong 			       struct genl_info *info,
13333f72e6fSBill Hong 			       struct l2tp_session *session,
13433f72e6fSBill Hong 			       u8 cmd)
13533f72e6fSBill Hong {
13633f72e6fSBill Hong 	struct sk_buff *msg;
13733f72e6fSBill Hong 	int ret;
13833f72e6fSBill Hong 
13933f72e6fSBill Hong 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
14033f72e6fSBill Hong 	if (!msg)
14133f72e6fSBill Hong 		return -ENOMEM;
14233f72e6fSBill Hong 
14333f72e6fSBill Hong 	ret = l2tp_nl_session_send(msg, info->snd_portid, info->snd_seq,
14433f72e6fSBill Hong 				   NLM_F_ACK, session, cmd);
14533f72e6fSBill Hong 
146853effc5SMark Tomlinson 	if (ret >= 0) {
14756440d7eSEric Dumazet 		ret = genlmsg_multicast_allns(family, msg, 0, 0);
148853effc5SMark Tomlinson 		/* We don't care if no one is listening */
149853effc5SMark Tomlinson 		if (ret == -ESRCH)
150853effc5SMark Tomlinson 			ret = 0;
151853effc5SMark Tomlinson 		return ret;
152853effc5SMark Tomlinson 	}
15333f72e6fSBill Hong 
15433f72e6fSBill Hong 	nlmsg_free(msg);
15533f72e6fSBill Hong 
15633f72e6fSBill Hong 	return ret;
15733f72e6fSBill Hong }
15833f72e6fSBill Hong 
l2tp_nl_cmd_tunnel_create_get_addr(struct nlattr ** attrs,struct l2tp_tunnel_cfg * cfg)1590787840dSTom Parkin static int l2tp_nl_cmd_tunnel_create_get_addr(struct nlattr **attrs, struct l2tp_tunnel_cfg *cfg)
1600787840dSTom Parkin {
1610787840dSTom Parkin 	if (attrs[L2TP_ATTR_UDP_SPORT])
1620787840dSTom Parkin 		cfg->local_udp_port = nla_get_u16(attrs[L2TP_ATTR_UDP_SPORT]);
1630787840dSTom Parkin 	if (attrs[L2TP_ATTR_UDP_DPORT])
1640787840dSTom Parkin 		cfg->peer_udp_port = nla_get_u16(attrs[L2TP_ATTR_UDP_DPORT]);
1650787840dSTom Parkin 	cfg->use_udp_checksums = nla_get_flag(attrs[L2TP_ATTR_UDP_CSUM]);
1660787840dSTom Parkin 
1670787840dSTom Parkin 	/* Must have either AF_INET or AF_INET6 address for source and destination */
1680787840dSTom Parkin #if IS_ENABLED(CONFIG_IPV6)
1690787840dSTom Parkin 	if (attrs[L2TP_ATTR_IP6_SADDR] && attrs[L2TP_ATTR_IP6_DADDR]) {
1700787840dSTom Parkin 		cfg->local_ip6 = nla_data(attrs[L2TP_ATTR_IP6_SADDR]);
1710787840dSTom Parkin 		cfg->peer_ip6 = nla_data(attrs[L2TP_ATTR_IP6_DADDR]);
1720787840dSTom Parkin 		cfg->udp6_zero_tx_checksums = nla_get_flag(attrs[L2TP_ATTR_UDP_ZERO_CSUM6_TX]);
1730787840dSTom Parkin 		cfg->udp6_zero_rx_checksums = nla_get_flag(attrs[L2TP_ATTR_UDP_ZERO_CSUM6_RX]);
1740787840dSTom Parkin 		return 0;
1750787840dSTom Parkin 	}
1760787840dSTom Parkin #endif
1770787840dSTom Parkin 	if (attrs[L2TP_ATTR_IP_SADDR] && attrs[L2TP_ATTR_IP_DADDR]) {
1780787840dSTom Parkin 		cfg->local_ip.s_addr = nla_get_in_addr(attrs[L2TP_ATTR_IP_SADDR]);
1790787840dSTom Parkin 		cfg->peer_ip.s_addr = nla_get_in_addr(attrs[L2TP_ATTR_IP_DADDR]);
1800787840dSTom Parkin 		return 0;
1810787840dSTom Parkin 	}
1820787840dSTom Parkin 	return -EINVAL;
1830787840dSTom Parkin }
1840787840dSTom Parkin 
l2tp_nl_cmd_tunnel_create(struct sk_buff * skb,struct genl_info * info)185309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info)
186309795f4SJames Chapman {
187309795f4SJames Chapman 	u32 tunnel_id;
188309795f4SJames Chapman 	u32 peer_tunnel_id;
189309795f4SJames Chapman 	int proto_version;
1900787840dSTom Parkin 	int fd = -1;
191309795f4SJames Chapman 	int ret = 0;
192309795f4SJames Chapman 	struct l2tp_tunnel_cfg cfg = { 0, };
193309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
194309795f4SJames Chapman 	struct net *net = genl_info_net(info);
1959f7da9a0STom Parkin 	struct nlattr **attrs = info->attrs;
196309795f4SJames Chapman 
1979f7da9a0STom Parkin 	if (!attrs[L2TP_ATTR_CONN_ID]) {
198309795f4SJames Chapman 		ret = -EINVAL;
199309795f4SJames Chapman 		goto out;
200309795f4SJames Chapman 	}
2019f7da9a0STom Parkin 	tunnel_id = nla_get_u32(attrs[L2TP_ATTR_CONN_ID]);
202309795f4SJames Chapman 
2039f7da9a0STom Parkin 	if (!attrs[L2TP_ATTR_PEER_CONN_ID]) {
204309795f4SJames Chapman 		ret = -EINVAL;
205309795f4SJames Chapman 		goto out;
206309795f4SJames Chapman 	}
2079f7da9a0STom Parkin 	peer_tunnel_id = nla_get_u32(attrs[L2TP_ATTR_PEER_CONN_ID]);
208309795f4SJames Chapman 
2099f7da9a0STom Parkin 	if (!attrs[L2TP_ATTR_PROTO_VERSION]) {
210309795f4SJames Chapman 		ret = -EINVAL;
211309795f4SJames Chapman 		goto out;
212309795f4SJames Chapman 	}
2139f7da9a0STom Parkin 	proto_version = nla_get_u8(attrs[L2TP_ATTR_PROTO_VERSION]);
214309795f4SJames Chapman 
2159f7da9a0STom Parkin 	if (!attrs[L2TP_ATTR_ENCAP_TYPE]) {
216309795f4SJames Chapman 		ret = -EINVAL;
217309795f4SJames Chapman 		goto out;
218309795f4SJames Chapman 	}
2199f7da9a0STom Parkin 	cfg.encap = nla_get_u16(attrs[L2TP_ATTR_ENCAP_TYPE]);
220309795f4SJames Chapman 
2210787840dSTom Parkin 	/* Managed tunnels take the tunnel socket from userspace.
2220787840dSTom Parkin 	 * Unmanaged tunnels must call out the source and destination addresses
2230787840dSTom Parkin 	 * for the kernel to create the tunnel socket itself.
2240787840dSTom Parkin 	 */
2259f7da9a0STom Parkin 	if (attrs[L2TP_ATTR_FD]) {
2269f7da9a0STom Parkin 		fd = nla_get_u32(attrs[L2TP_ATTR_FD]);
227789a4a2cSJames Chapman 	} else {
2280787840dSTom Parkin 		ret = l2tp_nl_cmd_tunnel_create_get_addr(attrs, &cfg);
2290787840dSTom Parkin 		if (ret < 0)
230f9bac8dfSChris Elston 			goto out;
231f9bac8dfSChris Elston 	}
232309795f4SJames Chapman 
233309795f4SJames Chapman 	ret = -EINVAL;
234309795f4SJames Chapman 	switch (cfg.encap) {
235309795f4SJames Chapman 	case L2TP_ENCAPTYPE_UDP:
236309795f4SJames Chapman 	case L2TP_ENCAPTYPE_IP:
237c9ccd4c6STom Parkin 		ret = l2tp_tunnel_create(fd, proto_version, tunnel_id,
238309795f4SJames Chapman 					 peer_tunnel_id, &cfg, &tunnel);
239309795f4SJames Chapman 		break;
240309795f4SJames Chapman 	}
241309795f4SJames Chapman 
2426b9f3423SGuillaume Nault 	if (ret < 0)
2436b9f3423SGuillaume Nault 		goto out;
2446b9f3423SGuillaume Nault 
245abe7a1a7SJames Chapman 	refcount_inc(&tunnel->ref_count);
2466b9f3423SGuillaume Nault 	ret = l2tp_tunnel_register(tunnel, net, &cfg);
2476b9f3423SGuillaume Nault 	if (ret < 0) {
2486b9f3423SGuillaume Nault 		kfree(tunnel);
2496b9f3423SGuillaume Nault 		goto out;
2506b9f3423SGuillaume Nault 	}
2516b9f3423SGuillaume Nault 	ret = l2tp_tunnel_notify(&l2tp_nl_family, info, tunnel,
2526b9f3423SGuillaume Nault 				 L2TP_CMD_TUNNEL_CREATE);
253abe7a1a7SJames Chapman 	l2tp_tunnel_put(tunnel);
2546b9f3423SGuillaume Nault 
255309795f4SJames Chapman out:
256309795f4SJames Chapman 	return ret;
257309795f4SJames Chapman }
258309795f4SJames Chapman 
l2tp_nl_cmd_tunnel_delete(struct sk_buff * skb,struct genl_info * info)259309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_delete(struct sk_buff *skb, struct genl_info *info)
260309795f4SJames Chapman {
261309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
262309795f4SJames Chapman 	u32 tunnel_id;
263309795f4SJames Chapman 	int ret = 0;
264309795f4SJames Chapman 	struct net *net = genl_info_net(info);
265309795f4SJames Chapman 
266309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_CONN_ID]) {
267309795f4SJames Chapman 		ret = -EINVAL;
268309795f4SJames Chapman 		goto out;
269309795f4SJames Chapman 	}
270309795f4SJames Chapman 	tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
271309795f4SJames Chapman 
272bb0a32ceSGuillaume Nault 	tunnel = l2tp_tunnel_get(net, tunnel_id);
273bb0a32ceSGuillaume Nault 	if (!tunnel) {
274309795f4SJames Chapman 		ret = -ENODEV;
275309795f4SJames Chapman 		goto out;
276309795f4SJames Chapman 	}
277309795f4SJames Chapman 
27833f72e6fSBill Hong 	l2tp_tunnel_notify(&l2tp_nl_family, info,
27933f72e6fSBill Hong 			   tunnel, L2TP_CMD_TUNNEL_DELETE);
28033f72e6fSBill Hong 
2814dc12ffeSJiri Slaby 	l2tp_tunnel_delete(tunnel);
282309795f4SJames Chapman 
283abe7a1a7SJames Chapman 	l2tp_tunnel_put(tunnel);
284bb0a32ceSGuillaume Nault 
285309795f4SJames Chapman out:
286309795f4SJames Chapman 	return ret;
287309795f4SJames Chapman }
288309795f4SJames Chapman 
l2tp_nl_cmd_tunnel_modify(struct sk_buff * skb,struct genl_info * info)289309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_modify(struct sk_buff *skb, struct genl_info *info)
290309795f4SJames Chapman {
291309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
292309795f4SJames Chapman 	u32 tunnel_id;
293309795f4SJames Chapman 	int ret = 0;
294309795f4SJames Chapman 	struct net *net = genl_info_net(info);
295309795f4SJames Chapman 
296309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_CONN_ID]) {
297309795f4SJames Chapman 		ret = -EINVAL;
298309795f4SJames Chapman 		goto out;
299309795f4SJames Chapman 	}
300309795f4SJames Chapman 	tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
301309795f4SJames Chapman 
3028c0e4215SGuillaume Nault 	tunnel = l2tp_tunnel_get(net, tunnel_id);
3038c0e4215SGuillaume Nault 	if (!tunnel) {
304309795f4SJames Chapman 		ret = -ENODEV;
305309795f4SJames Chapman 		goto out;
306309795f4SJames Chapman 	}
307309795f4SJames Chapman 
30833f72e6fSBill Hong 	ret = l2tp_tunnel_notify(&l2tp_nl_family, info,
30933f72e6fSBill Hong 				 tunnel, L2TP_CMD_TUNNEL_MODIFY);
31033f72e6fSBill Hong 
311abe7a1a7SJames Chapman 	l2tp_tunnel_put(tunnel);
3128c0e4215SGuillaume Nault 
313309795f4SJames Chapman out:
314309795f4SJames Chapman 	return ret;
315309795f4SJames Chapman }
316309795f4SJames Chapman 
317584ca31fSTom Parkin #if IS_ENABLED(CONFIG_IPV6)
l2tp_nl_tunnel_send_addr6(struct sk_buff * skb,struct sock * sk,enum l2tp_encap_type encap)318584ca31fSTom Parkin static int l2tp_nl_tunnel_send_addr6(struct sk_buff *skb, struct sock *sk,
319584ca31fSTom Parkin 				     enum l2tp_encap_type encap)
320584ca31fSTom Parkin {
321584ca31fSTom Parkin 	struct inet_sock *inet = inet_sk(sk);
322584ca31fSTom Parkin 	struct ipv6_pinfo *np = inet6_sk(sk);
323584ca31fSTom Parkin 
324584ca31fSTom Parkin 	switch (encap) {
325584ca31fSTom Parkin 	case L2TP_ENCAPTYPE_UDP:
326584ca31fSTom Parkin 		if (udp_get_no_check6_tx(sk) &&
327584ca31fSTom Parkin 		    nla_put_flag(skb, L2TP_ATTR_UDP_ZERO_CSUM6_TX))
328584ca31fSTom Parkin 			return -1;
329584ca31fSTom Parkin 		if (udp_get_no_check6_rx(sk) &&
330584ca31fSTom Parkin 		    nla_put_flag(skb, L2TP_ATTR_UDP_ZERO_CSUM6_RX))
331584ca31fSTom Parkin 			return -1;
332584ca31fSTom Parkin 		if (nla_put_u16(skb, L2TP_ATTR_UDP_SPORT, ntohs(inet->inet_sport)) ||
333584ca31fSTom Parkin 		    nla_put_u16(skb, L2TP_ATTR_UDP_DPORT, ntohs(inet->inet_dport)))
334584ca31fSTom Parkin 			return -1;
335584ca31fSTom Parkin 		fallthrough;
336584ca31fSTom Parkin 	case L2TP_ENCAPTYPE_IP:
337584ca31fSTom Parkin 		if (nla_put_in6_addr(skb, L2TP_ATTR_IP6_SADDR, &np->saddr) ||
338584ca31fSTom Parkin 		    nla_put_in6_addr(skb, L2TP_ATTR_IP6_DADDR, &sk->sk_v6_daddr))
339584ca31fSTom Parkin 			return -1;
340584ca31fSTom Parkin 		break;
341584ca31fSTom Parkin 	}
342584ca31fSTom Parkin 	return 0;
343584ca31fSTom Parkin }
344584ca31fSTom Parkin #endif
345584ca31fSTom Parkin 
l2tp_nl_tunnel_send_addr4(struct sk_buff * skb,struct sock * sk,enum l2tp_encap_type encap)346584ca31fSTom Parkin static int l2tp_nl_tunnel_send_addr4(struct sk_buff *skb, struct sock *sk,
347584ca31fSTom Parkin 				     enum l2tp_encap_type encap)
348584ca31fSTom Parkin {
349584ca31fSTom Parkin 	struct inet_sock *inet = inet_sk(sk);
350584ca31fSTom Parkin 
351584ca31fSTom Parkin 	switch (encap) {
352584ca31fSTom Parkin 	case L2TP_ENCAPTYPE_UDP:
353584ca31fSTom Parkin 		if (nla_put_u8(skb, L2TP_ATTR_UDP_CSUM, !sk->sk_no_check_tx) ||
354584ca31fSTom Parkin 		    nla_put_u16(skb, L2TP_ATTR_UDP_SPORT, ntohs(inet->inet_sport)) ||
355584ca31fSTom Parkin 		    nla_put_u16(skb, L2TP_ATTR_UDP_DPORT, ntohs(inet->inet_dport)))
356584ca31fSTom Parkin 			return -1;
357584ca31fSTom Parkin 		fallthrough;
358584ca31fSTom Parkin 	case L2TP_ENCAPTYPE_IP:
359584ca31fSTom Parkin 		if (nla_put_in_addr(skb, L2TP_ATTR_IP_SADDR, inet->inet_saddr) ||
360584ca31fSTom Parkin 		    nla_put_in_addr(skb, L2TP_ATTR_IP_DADDR, inet->inet_daddr))
361584ca31fSTom Parkin 			return -1;
362584ca31fSTom Parkin 		break;
363584ca31fSTom Parkin 	}
364584ca31fSTom Parkin 
365584ca31fSTom Parkin 	return 0;
366584ca31fSTom Parkin }
367584ca31fSTom Parkin 
368584ca31fSTom Parkin /* Append attributes for the tunnel address, handling the different attribute types
369584ca31fSTom Parkin  * used for different tunnel encapsulation and AF_INET v.s. AF_INET6.
370584ca31fSTom Parkin  */
l2tp_nl_tunnel_send_addr(struct sk_buff * skb,struct l2tp_tunnel * tunnel)371584ca31fSTom Parkin static int l2tp_nl_tunnel_send_addr(struct sk_buff *skb, struct l2tp_tunnel *tunnel)
372584ca31fSTom Parkin {
373584ca31fSTom Parkin 	struct sock *sk = tunnel->sock;
374584ca31fSTom Parkin 
375584ca31fSTom Parkin 	if (!sk)
376584ca31fSTom Parkin 		return 0;
377584ca31fSTom Parkin 
378584ca31fSTom Parkin #if IS_ENABLED(CONFIG_IPV6)
379584ca31fSTom Parkin 	if (sk->sk_family == AF_INET6)
380584ca31fSTom Parkin 		return l2tp_nl_tunnel_send_addr6(skb, sk, tunnel->encap);
381584ca31fSTom Parkin #endif
382584ca31fSTom Parkin 	return l2tp_nl_tunnel_send_addr4(skb, sk, tunnel->encap);
383584ca31fSTom Parkin }
384584ca31fSTom Parkin 
l2tp_nl_tunnel_send(struct sk_buff * skb,u32 portid,u32 seq,int flags,struct l2tp_tunnel * tunnel,u8 cmd)38515e47304SEric W. Biederman static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 portid, u32 seq, int flags,
38633f72e6fSBill Hong 			       struct l2tp_tunnel *tunnel, u8 cmd)
387309795f4SJames Chapman {
388309795f4SJames Chapman 	void *hdr;
389309795f4SJames Chapman 	struct nlattr *nest;
390309795f4SJames Chapman 
39133f72e6fSBill Hong 	hdr = genlmsg_put(skb, portid, seq, &l2tp_nl_family, flags, cmd);
3927f8436a1SWei Yongjun 	if (!hdr)
3937f8436a1SWei Yongjun 		return -EMSGSIZE;
394309795f4SJames Chapman 
39560aed2abSDavid S. Miller 	if (nla_put_u8(skb, L2TP_ATTR_PROTO_VERSION, tunnel->version) ||
39660aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id) ||
39760aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id) ||
398eee049c0STom Parkin 	    nla_put_u32(skb, L2TP_ATTR_DEBUG, 0) ||
39960aed2abSDavid S. Miller 	    nla_put_u16(skb, L2TP_ATTR_ENCAP_TYPE, tunnel->encap))
40060aed2abSDavid S. Miller 		goto nla_put_failure;
401309795f4SJames Chapman 
402ae0be8deSMichal Kubecek 	nest = nla_nest_start_noflag(skb, L2TP_ATTR_STATS);
4030febc7b3STom Parkin 	if (!nest)
404309795f4SJames Chapman 		goto nla_put_failure;
405309795f4SJames Chapman 
4061c714a92SNicolas Dichtel 	if (nla_put_u64_64bit(skb, L2TP_ATTR_TX_PACKETS,
4071c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.tx_packets),
4081c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
4091c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_TX_BYTES,
4101c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.tx_bytes),
4111c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
4121c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_TX_ERRORS,
4131c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.tx_errors),
4141c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
4151c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_PACKETS,
4161c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.rx_packets),
4171c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
4181c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_BYTES,
4191c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.rx_bytes),
4201c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
4211c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_SEQ_DISCARDS,
4221c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.rx_seq_discards),
4231c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
4243f47cb4cSTom Parkin 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_COOKIE_DISCARDS,
4253f47cb4cSTom Parkin 			      atomic_long_read(&tunnel->stats.rx_cookie_discards),
4263f47cb4cSTom Parkin 			      L2TP_ATTR_STATS_PAD) ||
4271c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_OOS_PACKETS,
4281c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.rx_oos_packets),
4291c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
4301c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_ERRORS,
4311c714a92SNicolas Dichtel 			      atomic_long_read(&tunnel->stats.rx_errors),
4323e59e885SMatthias Schiffer 			      L2TP_ATTR_STATS_PAD) ||
4333e59e885SMatthias Schiffer 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_INVALID,
4343e59e885SMatthias Schiffer 			      atomic_long_read(&tunnel->stats.rx_invalid),
4351c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD))
43660aed2abSDavid S. Miller 		goto nla_put_failure;
437309795f4SJames Chapman 	nla_nest_end(skb, nest);
438309795f4SJames Chapman 
439584ca31fSTom Parkin 	if (l2tp_nl_tunnel_send_addr(skb, tunnel))
440584ca31fSTom Parkin 		goto nla_put_failure;
441309795f4SJames Chapman 
442053c095aSJohannes Berg 	genlmsg_end(skb, hdr);
443053c095aSJohannes Berg 	return 0;
444309795f4SJames Chapman 
445309795f4SJames Chapman nla_put_failure:
446309795f4SJames Chapman 	genlmsg_cancel(skb, hdr);
447309795f4SJames Chapman 	return -1;
448309795f4SJames Chapman }
449309795f4SJames Chapman 
l2tp_nl_cmd_tunnel_get(struct sk_buff * skb,struct genl_info * info)450309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_get(struct sk_buff *skb, struct genl_info *info)
451309795f4SJames Chapman {
452309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
453309795f4SJames Chapman 	struct sk_buff *msg;
454309795f4SJames Chapman 	u32 tunnel_id;
455309795f4SJames Chapman 	int ret = -ENOBUFS;
456309795f4SJames Chapman 	struct net *net = genl_info_net(info);
457309795f4SJames Chapman 
458309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_CONN_ID]) {
459309795f4SJames Chapman 		ret = -EINVAL;
4604e4b21daSGuillaume Nault 		goto err;
461309795f4SJames Chapman 	}
462309795f4SJames Chapman 
463309795f4SJames Chapman 	tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
464309795f4SJames Chapman 
46558050fceSThomas Graf 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
466309795f4SJames Chapman 	if (!msg) {
467309795f4SJames Chapman 		ret = -ENOMEM;
4684e4b21daSGuillaume Nault 		goto err;
4694e4b21daSGuillaume Nault 	}
4704e4b21daSGuillaume Nault 
4714e4b21daSGuillaume Nault 	tunnel = l2tp_tunnel_get(net, tunnel_id);
4724e4b21daSGuillaume Nault 	if (!tunnel) {
4734e4b21daSGuillaume Nault 		ret = -ENODEV;
4744e4b21daSGuillaume Nault 		goto err_nlmsg;
475309795f4SJames Chapman 	}
476309795f4SJames Chapman 
47715e47304SEric W. Biederman 	ret = l2tp_nl_tunnel_send(msg, info->snd_portid, info->snd_seq,
47833f72e6fSBill Hong 				  NLM_F_ACK, tunnel, L2TP_CMD_TUNNEL_GET);
479309795f4SJames Chapman 	if (ret < 0)
4804e4b21daSGuillaume Nault 		goto err_nlmsg_tunnel;
4814e4b21daSGuillaume Nault 
482abe7a1a7SJames Chapman 	l2tp_tunnel_put(tunnel);
483309795f4SJames Chapman 
48415e47304SEric W. Biederman 	return genlmsg_unicast(net, msg, info->snd_portid);
485309795f4SJames Chapman 
4864e4b21daSGuillaume Nault err_nlmsg_tunnel:
487abe7a1a7SJames Chapman 	l2tp_tunnel_put(tunnel);
4884e4b21daSGuillaume Nault err_nlmsg:
489309795f4SJames Chapman 	nlmsg_free(msg);
4904e4b21daSGuillaume Nault err:
491309795f4SJames Chapman 	return ret;
492309795f4SJames Chapman }
493309795f4SJames Chapman 
4941f4c3dceSJames Chapman struct l2tp_nl_cb_data {
4951f4c3dceSJames Chapman 	unsigned long tkey;
4961f4c3dceSJames Chapman 	unsigned long skey;
4971f4c3dceSJames Chapman };
4981f4c3dceSJames Chapman 
l2tp_nl_cmd_tunnel_dump(struct sk_buff * skb,struct netlink_callback * cb)499309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_dump(struct sk_buff *skb, struct netlink_callback *cb)
500309795f4SJames Chapman {
5011f4c3dceSJames Chapman 	struct l2tp_nl_cb_data *cbd = (void *)&cb->ctx[0];
5021f4c3dceSJames Chapman 	unsigned long key = cbd->tkey;
503309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
504309795f4SJames Chapman 	struct net *net = sock_net(skb->sk);
505309795f4SJames Chapman 
506309795f4SJames Chapman 	for (;;) {
5071f4c3dceSJames Chapman 		tunnel = l2tp_tunnel_get_next(net, &key);
5080febc7b3STom Parkin 		if (!tunnel)
509309795f4SJames Chapman 			goto out;
510309795f4SJames Chapman 
51115e47304SEric W. Biederman 		if (l2tp_nl_tunnel_send(skb, NETLINK_CB(cb->skb).portid,
512309795f4SJames Chapman 					cb->nlh->nlmsg_seq, NLM_F_MULTI,
5135846c131SGuillaume Nault 					tunnel, L2TP_CMD_TUNNEL_GET) < 0) {
514abe7a1a7SJames Chapman 			l2tp_tunnel_put(tunnel);
515309795f4SJames Chapman 			goto out;
5165846c131SGuillaume Nault 		}
517abe7a1a7SJames Chapman 		l2tp_tunnel_put(tunnel);
518309795f4SJames Chapman 
5191f4c3dceSJames Chapman 		key++;
520309795f4SJames Chapman 	}
521309795f4SJames Chapman 
522309795f4SJames Chapman out:
5231f4c3dceSJames Chapman 	cbd->tkey = key;
524309795f4SJames Chapman 
525309795f4SJames Chapman 	return skb->len;
526309795f4SJames Chapman }
527309795f4SJames Chapman 
l2tp_nl_cmd_session_create(struct sk_buff * skb,struct genl_info * info)528309795f4SJames Chapman static int l2tp_nl_cmd_session_create(struct sk_buff *skb, struct genl_info *info)
529309795f4SJames Chapman {
530309795f4SJames Chapman 	u32 tunnel_id = 0;
531309795f4SJames Chapman 	u32 session_id;
532309795f4SJames Chapman 	u32 peer_session_id;
533309795f4SJames Chapman 	int ret = 0;
534309795f4SJames Chapman 	struct l2tp_tunnel *tunnel;
535309795f4SJames Chapman 	struct l2tp_session *session;
536309795f4SJames Chapman 	struct l2tp_session_cfg cfg = { 0, };
537309795f4SJames Chapman 	struct net *net = genl_info_net(info);
538309795f4SJames Chapman 
539309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_CONN_ID]) {
540309795f4SJames Chapman 		ret = -EINVAL;
541309795f4SJames Chapman 		goto out;
542309795f4SJames Chapman 	}
543e702c120SGuillaume Nault 
544309795f4SJames Chapman 	tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
545e702c120SGuillaume Nault 	tunnel = l2tp_tunnel_get(net, tunnel_id);
546309795f4SJames Chapman 	if (!tunnel) {
547309795f4SJames Chapman 		ret = -ENODEV;
548309795f4SJames Chapman 		goto out;
549309795f4SJames Chapman 	}
550309795f4SJames Chapman 
551309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_SESSION_ID]) {
552309795f4SJames Chapman 		ret = -EINVAL;
553e702c120SGuillaume Nault 		goto out_tunnel;
554309795f4SJames Chapman 	}
555309795f4SJames Chapman 	session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]);
556309795f4SJames Chapman 
557309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_PEER_SESSION_ID]) {
558309795f4SJames Chapman 		ret = -EINVAL;
559e702c120SGuillaume Nault 		goto out_tunnel;
560309795f4SJames Chapman 	}
561309795f4SJames Chapman 	peer_session_id = nla_get_u32(info->attrs[L2TP_ATTR_PEER_SESSION_ID]);
562309795f4SJames Chapman 
563309795f4SJames Chapman 	if (!info->attrs[L2TP_ATTR_PW_TYPE]) {
564309795f4SJames Chapman 		ret = -EINVAL;
565e702c120SGuillaume Nault 		goto out_tunnel;
566309795f4SJames Chapman 	}
567309795f4SJames Chapman 	cfg.pw_type = nla_get_u16(info->attrs[L2TP_ATTR_PW_TYPE]);
568309795f4SJames Chapman 	if (cfg.pw_type >= __L2TP_PWTYPE_MAX) {
569309795f4SJames Chapman 		ret = -EINVAL;
570e702c120SGuillaume Nault 		goto out_tunnel;
571309795f4SJames Chapman 	}
572309795f4SJames Chapman 
573de9bada5SGuillaume Nault 	/* L2TPv2 only accepts PPP pseudo-wires */
574de9bada5SGuillaume Nault 	if (tunnel->version == 2 && cfg.pw_type != L2TP_PWTYPE_PPP) {
575de9bada5SGuillaume Nault 		ret = -EPROTONOSUPPORT;
576de9bada5SGuillaume Nault 		goto out_tunnel;
577de9bada5SGuillaume Nault 	}
578de9bada5SGuillaume Nault 
579309795f4SJames Chapman 	if (tunnel->version > 2) {
580dfffc97dSLorenzo Bianconi 		if (info->attrs[L2TP_ATTR_L2SPEC_TYPE]) {
581309795f4SJames Chapman 			cfg.l2specific_type = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_TYPE]);
582dfffc97dSLorenzo Bianconi 			if (cfg.l2specific_type != L2TP_L2SPECTYPE_DEFAULT &&
583dfffc97dSLorenzo Bianconi 			    cfg.l2specific_type != L2TP_L2SPECTYPE_NONE) {
584dfffc97dSLorenzo Bianconi 				ret = -EINVAL;
585dfffc97dSLorenzo Bianconi 				goto out_tunnel;
586dfffc97dSLorenzo Bianconi 			}
587dfffc97dSLorenzo Bianconi 		} else {
588dfffc97dSLorenzo Bianconi 			cfg.l2specific_type = L2TP_L2SPECTYPE_DEFAULT;
589dfffc97dSLorenzo Bianconi 		}
590309795f4SJames Chapman 
591309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_COOKIE]) {
592309795f4SJames Chapman 			u16 len = nla_len(info->attrs[L2TP_ATTR_COOKIE]);
593b71a61ccSTom Parkin 
594309795f4SJames Chapman 			if (len > 8) {
595309795f4SJames Chapman 				ret = -EINVAL;
596e702c120SGuillaume Nault 				goto out_tunnel;
597309795f4SJames Chapman 			}
598309795f4SJames Chapman 			cfg.cookie_len = len;
599309795f4SJames Chapman 			memcpy(&cfg.cookie[0], nla_data(info->attrs[L2TP_ATTR_COOKIE]), len);
600309795f4SJames Chapman 		}
601309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_PEER_COOKIE]) {
602309795f4SJames Chapman 			u16 len = nla_len(info->attrs[L2TP_ATTR_PEER_COOKIE]);
603b71a61ccSTom Parkin 
604309795f4SJames Chapman 			if (len > 8) {
605309795f4SJames Chapman 				ret = -EINVAL;
606e702c120SGuillaume Nault 				goto out_tunnel;
607309795f4SJames Chapman 			}
608309795f4SJames Chapman 			cfg.peer_cookie_len = len;
609309795f4SJames Chapman 			memcpy(&cfg.peer_cookie[0], nla_data(info->attrs[L2TP_ATTR_PEER_COOKIE]), len);
610309795f4SJames Chapman 		}
611309795f4SJames Chapman 		if (info->attrs[L2TP_ATTR_IFNAME])
612309795f4SJames Chapman 			cfg.ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]);
613309795f4SJames Chapman 	}
614309795f4SJames Chapman 
615309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_RECV_SEQ])
616309795f4SJames Chapman 		cfg.recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]);
617309795f4SJames Chapman 
618309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_SEND_SEQ])
619309795f4SJames Chapman 		cfg.send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]);
620309795f4SJames Chapman 
621309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_LNS_MODE])
622309795f4SJames Chapman 		cfg.lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]);
623309795f4SJames Chapman 
624309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_RECV_TIMEOUT])
625309795f4SJames Chapman 		cfg.reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]);
626309795f4SJames Chapman 
627f1f39f91Sstephen hemminger #ifdef CONFIG_MODULES
6280febc7b3STom Parkin 	if (!l2tp_nl_cmd_ops[cfg.pw_type]) {
629f1f39f91Sstephen hemminger 		genl_unlock();
630f1f39f91Sstephen hemminger 		request_module("net-l2tp-type-%u", cfg.pw_type);
631f1f39f91Sstephen hemminger 		genl_lock();
632f1f39f91Sstephen hemminger 	}
633f1f39f91Sstephen hemminger #endif
6340febc7b3STom Parkin 	if (!l2tp_nl_cmd_ops[cfg.pw_type] || !l2tp_nl_cmd_ops[cfg.pw_type]->session_create) {
635309795f4SJames Chapman 		ret = -EPROTONOSUPPORT;
636e702c120SGuillaume Nault 		goto out_tunnel;
637309795f4SJames Chapman 	}
638309795f4SJames Chapman 
639f026bc29SGuillaume Nault 	ret = l2tp_nl_cmd_ops[cfg.pw_type]->session_create(net, tunnel,
640f026bc29SGuillaume Nault 							   session_id,
641f026bc29SGuillaume Nault 							   peer_session_id,
642f026bc29SGuillaume Nault 							   &cfg);
643309795f4SJames Chapman 
64433f72e6fSBill Hong 	if (ret >= 0) {
6455f77c18eSJames Chapman 		session = l2tp_session_get(net, tunnel->sock, tunnel->version,
6465f77c18eSJames Chapman 					   tunnel_id, session_id);
6475e6a9e5aSGuillaume Nault 		if (session) {
64833f72e6fSBill Hong 			ret = l2tp_session_notify(&l2tp_nl_family, info, session,
64933f72e6fSBill Hong 						  L2TP_CMD_SESSION_CREATE);
650abe7a1a7SJames Chapman 			l2tp_session_put(session);
6515e6a9e5aSGuillaume Nault 		}
65233f72e6fSBill Hong 	}
65333f72e6fSBill Hong 
654e702c120SGuillaume Nault out_tunnel:
655abe7a1a7SJames Chapman 	l2tp_tunnel_put(tunnel);
656309795f4SJames Chapman out:
657309795f4SJames Chapman 	return ret;
658309795f4SJames Chapman }
659309795f4SJames Chapman 
l2tp_nl_cmd_session_delete(struct sk_buff * skb,struct genl_info * info)660309795f4SJames Chapman static int l2tp_nl_cmd_session_delete(struct sk_buff *skb, struct genl_info *info)
661309795f4SJames Chapman {
662309795f4SJames Chapman 	int ret = 0;
663309795f4SJames Chapman 	struct l2tp_session *session;
664309795f4SJames Chapman 	u16 pw_type;
665309795f4SJames Chapman 
666a4346210SGuillaume Nault 	session = l2tp_nl_session_get(info);
6670febc7b3STom Parkin 	if (!session) {
668309795f4SJames Chapman 		ret = -ENODEV;
669309795f4SJames Chapman 		goto out;
670309795f4SJames Chapman 	}
671309795f4SJames Chapman 
67233f72e6fSBill Hong 	l2tp_session_notify(&l2tp_nl_family, info,
67333f72e6fSBill Hong 			    session, L2TP_CMD_SESSION_DELETE);
67433f72e6fSBill Hong 
675309795f4SJames Chapman 	pw_type = session->pwtype;
676309795f4SJames Chapman 	if (pw_type < __L2TP_PWTYPE_MAX)
677309795f4SJames Chapman 		if (l2tp_nl_cmd_ops[pw_type] && l2tp_nl_cmd_ops[pw_type]->session_delete)
678628703f5STom Parkin 			l2tp_nl_cmd_ops[pw_type]->session_delete(session);
679309795f4SJames Chapman 
680abe7a1a7SJames Chapman 	l2tp_session_put(session);
6812777e2abSGuillaume Nault 
682309795f4SJames Chapman out:
683309795f4SJames Chapman 	return ret;
684309795f4SJames Chapman }
685309795f4SJames Chapman 
l2tp_nl_cmd_session_modify(struct sk_buff * skb,struct genl_info * info)686309795f4SJames Chapman static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *info)
687309795f4SJames Chapman {
688309795f4SJames Chapman 	int ret = 0;
689309795f4SJames Chapman 	struct l2tp_session *session;
690309795f4SJames Chapman 
691a4346210SGuillaume Nault 	session = l2tp_nl_session_get(info);
6920febc7b3STom Parkin 	if (!session) {
693309795f4SJames Chapman 		ret = -ENODEV;
694309795f4SJames Chapman 		goto out;
695309795f4SJames Chapman 	}
696309795f4SJames Chapman 
697309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_RECV_SEQ])
698309795f4SJames Chapman 		session->recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]);
699309795f4SJames Chapman 
700bb5016eaSGuillaume Nault 	if (info->attrs[L2TP_ATTR_SEND_SEQ]) {
70124256415SJames Chapman 		struct l2tp_tunnel *tunnel = session->tunnel;
70224256415SJames Chapman 
703309795f4SJames Chapman 		session->send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]);
70424256415SJames Chapman 		l2tp_session_set_header_len(session, tunnel->version, tunnel->encap);
705bb5016eaSGuillaume Nault 	}
706309795f4SJames Chapman 
707309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_LNS_MODE])
708309795f4SJames Chapman 		session->lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]);
709309795f4SJames Chapman 
710309795f4SJames Chapman 	if (info->attrs[L2TP_ATTR_RECV_TIMEOUT])
711309795f4SJames Chapman 		session->reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]);
712309795f4SJames Chapman 
71333f72e6fSBill Hong 	ret = l2tp_session_notify(&l2tp_nl_family, info,
71433f72e6fSBill Hong 				  session, L2TP_CMD_SESSION_MODIFY);
71533f72e6fSBill Hong 
716abe7a1a7SJames Chapman 	l2tp_session_put(session);
7172777e2abSGuillaume Nault 
718309795f4SJames Chapman out:
719309795f4SJames Chapman 	return ret;
720309795f4SJames Chapman }
721309795f4SJames Chapman 
l2tp_nl_session_send(struct sk_buff * skb,u32 portid,u32 seq,int flags,struct l2tp_session * session,u8 cmd)72215e47304SEric W. Biederman static int l2tp_nl_session_send(struct sk_buff *skb, u32 portid, u32 seq, int flags,
72333f72e6fSBill Hong 				struct l2tp_session *session, u8 cmd)
724309795f4SJames Chapman {
725309795f4SJames Chapman 	void *hdr;
726309795f4SJames Chapman 	struct nlattr *nest;
727309795f4SJames Chapman 	struct l2tp_tunnel *tunnel = session->tunnel;
728309795f4SJames Chapman 
72933f72e6fSBill Hong 	hdr = genlmsg_put(skb, portid, seq, &l2tp_nl_family, flags, cmd);
7307f8436a1SWei Yongjun 	if (!hdr)
7317f8436a1SWei Yongjun 		return -EMSGSIZE;
732309795f4SJames Chapman 
73360aed2abSDavid S. Miller 	if (nla_put_u32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id) ||
73460aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_SESSION_ID, session->session_id) ||
73560aed2abSDavid S. Miller 	    nla_put_u32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id) ||
7369f7da9a0STom Parkin 	    nla_put_u32(skb, L2TP_ATTR_PEER_SESSION_ID, session->peer_session_id) ||
737eee049c0STom Parkin 	    nla_put_u32(skb, L2TP_ATTR_DEBUG, 0) ||
738e9697e2eSGuillaume Nault 	    nla_put_u16(skb, L2TP_ATTR_PW_TYPE, session->pwtype))
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 &&
7449f7da9a0STom Parkin 	     nla_put(skb, L2TP_ATTR_COOKIE, session->cookie_len, session->cookie)) ||
74560aed2abSDavid S. Miller 	    (session->peer_cookie_len &&
7469f7da9a0STom Parkin 	     nla_put(skb, L2TP_ATTR_PEER_COOKIE, session->peer_cookie_len, session->peer_cookie)) ||
74760aed2abSDavid S. Miller 	    nla_put_u8(skb, L2TP_ATTR_RECV_SEQ, session->recv_seq) ||
74860aed2abSDavid S. Miller 	    nla_put_u8(skb, L2TP_ATTR_SEND_SEQ, session->send_seq) ||
74960aed2abSDavid S. Miller 	    nla_put_u8(skb, L2TP_ATTR_LNS_MODE, session->lns_mode) ||
750d6a61ec9SGuillaume Nault 	    (l2tp_tunnel_uses_xfrm(tunnel) &&
75160aed2abSDavid S. Miller 	     nla_put_u8(skb, L2TP_ATTR_USING_IPSEC, 1)) ||
75260aed2abSDavid S. Miller 	    (session->reorder_timeout &&
7532175d87cSNicolas Dichtel 	     nla_put_msecs(skb, L2TP_ATTR_RECV_TIMEOUT,
7542175d87cSNicolas Dichtel 			   session->reorder_timeout, L2TP_ATTR_PAD)))
75560aed2abSDavid S. Miller 		goto nla_put_failure;
7565de7aee5SJames Chapman 
757ae0be8deSMichal Kubecek 	nest = nla_nest_start_noflag(skb, L2TP_ATTR_STATS);
7580febc7b3STom Parkin 	if (!nest)
759309795f4SJames Chapman 		goto nla_put_failure;
7605de7aee5SJames Chapman 
7611c714a92SNicolas Dichtel 	if (nla_put_u64_64bit(skb, L2TP_ATTR_TX_PACKETS,
7621c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.tx_packets),
7631c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7641c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_TX_BYTES,
7651c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.tx_bytes),
7661c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7671c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_TX_ERRORS,
7681c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.tx_errors),
7691c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7701c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_PACKETS,
7711c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.rx_packets),
7721c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7731c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_BYTES,
7741c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.rx_bytes),
7751c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7761c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_SEQ_DISCARDS,
7771c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.rx_seq_discards),
7781c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7793f47cb4cSTom Parkin 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_COOKIE_DISCARDS,
7803f47cb4cSTom Parkin 			      atomic_long_read(&session->stats.rx_cookie_discards),
7813f47cb4cSTom Parkin 			      L2TP_ATTR_STATS_PAD) ||
7821c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_OOS_PACKETS,
7831c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.rx_oos_packets),
7841c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD) ||
7851c714a92SNicolas Dichtel 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_ERRORS,
7861c714a92SNicolas Dichtel 			      atomic_long_read(&session->stats.rx_errors),
7873e59e885SMatthias Schiffer 			      L2TP_ATTR_STATS_PAD) ||
7883e59e885SMatthias Schiffer 	    nla_put_u64_64bit(skb, L2TP_ATTR_RX_INVALID,
7893e59e885SMatthias Schiffer 			      atomic_long_read(&session->stats.rx_invalid),
7901c714a92SNicolas Dichtel 			      L2TP_ATTR_STATS_PAD))
79160aed2abSDavid S. Miller 		goto nla_put_failure;
792309795f4SJames Chapman 	nla_nest_end(skb, nest);
793309795f4SJames Chapman 
794053c095aSJohannes Berg 	genlmsg_end(skb, hdr);
795053c095aSJohannes Berg 	return 0;
796309795f4SJames Chapman 
797309795f4SJames Chapman  nla_put_failure:
798309795f4SJames Chapman 	genlmsg_cancel(skb, hdr);
799309795f4SJames Chapman 	return -1;
800309795f4SJames Chapman }
801309795f4SJames Chapman 
l2tp_nl_cmd_session_get(struct sk_buff * skb,struct genl_info * info)802309795f4SJames Chapman static int l2tp_nl_cmd_session_get(struct sk_buff *skb, struct genl_info *info)
803309795f4SJames Chapman {
804309795f4SJames Chapman 	struct l2tp_session *session;
805309795f4SJames Chapman 	struct sk_buff *msg;
806309795f4SJames Chapman 	int ret;
807309795f4SJames Chapman 
808a4346210SGuillaume Nault 	session = l2tp_nl_session_get(info);
8090febc7b3STom Parkin 	if (!session) {
810309795f4SJames Chapman 		ret = -ENODEV;
8112777e2abSGuillaume Nault 		goto err;
812309795f4SJames Chapman 	}
813309795f4SJames Chapman 
81458050fceSThomas Graf 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
815309795f4SJames Chapman 	if (!msg) {
816309795f4SJames Chapman 		ret = -ENOMEM;
8172777e2abSGuillaume Nault 		goto err_ref;
818309795f4SJames Chapman 	}
819309795f4SJames Chapman 
82015e47304SEric W. Biederman 	ret = l2tp_nl_session_send(msg, info->snd_portid, info->snd_seq,
82133f72e6fSBill Hong 				   0, session, L2TP_CMD_SESSION_GET);
822309795f4SJames Chapman 	if (ret < 0)
8232777e2abSGuillaume Nault 		goto err_ref_msg;
824309795f4SJames Chapman 
8252777e2abSGuillaume Nault 	ret = genlmsg_unicast(genl_info_net(info), msg, info->snd_portid);
826309795f4SJames Chapman 
827abe7a1a7SJames Chapman 	l2tp_session_put(session);
8282777e2abSGuillaume Nault 
8292777e2abSGuillaume Nault 	return ret;
8302777e2abSGuillaume Nault 
8312777e2abSGuillaume Nault err_ref_msg:
832309795f4SJames Chapman 	nlmsg_free(msg);
8332777e2abSGuillaume Nault err_ref:
834abe7a1a7SJames Chapman 	l2tp_session_put(session);
8352777e2abSGuillaume Nault err:
836309795f4SJames Chapman 	return ret;
837309795f4SJames Chapman }
838309795f4SJames Chapman 
l2tp_nl_cmd_session_dump(struct sk_buff * skb,struct netlink_callback * cb)839309795f4SJames Chapman static int l2tp_nl_cmd_session_dump(struct sk_buff *skb, struct netlink_callback *cb)
840309795f4SJames Chapman {
8411f4c3dceSJames Chapman 	struct l2tp_nl_cb_data *cbd = (void *)&cb->ctx[0];
842309795f4SJames Chapman 	struct net *net = sock_net(skb->sk);
843309795f4SJames Chapman 	struct l2tp_session *session;
844309795f4SJames Chapman 	struct l2tp_tunnel *tunnel = NULL;
8451f4c3dceSJames Chapman 	unsigned long tkey = cbd->tkey;
8461f4c3dceSJames Chapman 	unsigned long skey = cbd->skey;
847309795f4SJames Chapman 
848309795f4SJames Chapman 	for (;;) {
8490febc7b3STom Parkin 		if (!tunnel) {
8501f4c3dceSJames Chapman 			tunnel = l2tp_tunnel_get_next(net, &tkey);
8510febc7b3STom Parkin 			if (!tunnel)
852309795f4SJames Chapman 				goto out;
853309795f4SJames Chapman 		}
854309795f4SJames Chapman 
8551f4c3dceSJames Chapman 		session = l2tp_session_get_next(net, tunnel->sock, tunnel->version,
8561f4c3dceSJames Chapman 						tunnel->tunnel_id, &skey);
8570febc7b3STom Parkin 		if (!session) {
8581f4c3dceSJames Chapman 			tkey++;
859abe7a1a7SJames Chapman 			l2tp_tunnel_put(tunnel);
860309795f4SJames Chapman 			tunnel = NULL;
8611f4c3dceSJames Chapman 			skey = 0;
862309795f4SJames Chapman 			continue;
863309795f4SJames Chapman 		}
864309795f4SJames Chapman 
86515e47304SEric W. Biederman 		if (l2tp_nl_session_send(skb, NETLINK_CB(cb->skb).portid,
866309795f4SJames Chapman 					 cb->nlh->nlmsg_seq, NLM_F_MULTI,
867e08293a4SGuillaume Nault 					 session, L2TP_CMD_SESSION_GET) < 0) {
868abe7a1a7SJames Chapman 			l2tp_session_put(session);
869abe7a1a7SJames Chapman 			l2tp_tunnel_put(tunnel);
870309795f4SJames Chapman 			break;
871e08293a4SGuillaume Nault 		}
872abe7a1a7SJames Chapman 		l2tp_session_put(session);
873309795f4SJames Chapman 
8741f4c3dceSJames Chapman 		skey++;
875309795f4SJames Chapman 	}
876309795f4SJames Chapman 
877309795f4SJames Chapman out:
8781f4c3dceSJames Chapman 	cbd->tkey = tkey;
8791f4c3dceSJames Chapman 	cbd->skey = skey;
880309795f4SJames Chapman 
881309795f4SJames Chapman 	return skb->len;
882309795f4SJames Chapman }
883309795f4SJames Chapman 
884f5bb341eSstephen hemminger static const struct nla_policy l2tp_nl_policy[L2TP_ATTR_MAX + 1] = {
885309795f4SJames Chapman 	[L2TP_ATTR_NONE]		= { .type = NLA_UNSPEC, },
886309795f4SJames Chapman 	[L2TP_ATTR_PW_TYPE]		= { .type = NLA_U16, },
887309795f4SJames Chapman 	[L2TP_ATTR_ENCAP_TYPE]		= { .type = NLA_U16, },
888309795f4SJames Chapman 	[L2TP_ATTR_OFFSET]		= { .type = NLA_U16, },
889309795f4SJames Chapman 	[L2TP_ATTR_DATA_SEQ]		= { .type = NLA_U8, },
890309795f4SJames Chapman 	[L2TP_ATTR_L2SPEC_TYPE]		= { .type = NLA_U8, },
891309795f4SJames Chapman 	[L2TP_ATTR_L2SPEC_LEN]		= { .type = NLA_U8, },
892309795f4SJames Chapman 	[L2TP_ATTR_PROTO_VERSION]	= { .type = NLA_U8, },
893309795f4SJames Chapman 	[L2TP_ATTR_CONN_ID]		= { .type = NLA_U32, },
894309795f4SJames Chapman 	[L2TP_ATTR_PEER_CONN_ID]	= { .type = NLA_U32, },
895309795f4SJames Chapman 	[L2TP_ATTR_SESSION_ID]		= { .type = NLA_U32, },
896309795f4SJames Chapman 	[L2TP_ATTR_PEER_SESSION_ID]	= { .type = NLA_U32, },
897309795f4SJames Chapman 	[L2TP_ATTR_UDP_CSUM]		= { .type = NLA_U8, },
898309795f4SJames Chapman 	[L2TP_ATTR_VLAN_ID]		= { .type = NLA_U16, },
899309795f4SJames Chapman 	[L2TP_ATTR_DEBUG]		= { .type = NLA_U32, },
900309795f4SJames Chapman 	[L2TP_ATTR_RECV_SEQ]		= { .type = NLA_U8, },
901309795f4SJames Chapman 	[L2TP_ATTR_SEND_SEQ]		= { .type = NLA_U8, },
902309795f4SJames Chapman 	[L2TP_ATTR_LNS_MODE]		= { .type = NLA_U8, },
903309795f4SJames Chapman 	[L2TP_ATTR_USING_IPSEC]		= { .type = NLA_U8, },
904309795f4SJames Chapman 	[L2TP_ATTR_RECV_TIMEOUT]	= { .type = NLA_MSECS, },
905309795f4SJames Chapman 	[L2TP_ATTR_FD]			= { .type = NLA_U32, },
906309795f4SJames Chapman 	[L2TP_ATTR_IP_SADDR]		= { .type = NLA_U32, },
907309795f4SJames Chapman 	[L2TP_ATTR_IP_DADDR]		= { .type = NLA_U32, },
908309795f4SJames Chapman 	[L2TP_ATTR_UDP_SPORT]		= { .type = NLA_U16, },
909309795f4SJames Chapman 	[L2TP_ATTR_UDP_DPORT]		= { .type = NLA_U16, },
910309795f4SJames Chapman 	[L2TP_ATTR_MTU]			= { .type = NLA_U16, },
911309795f4SJames Chapman 	[L2TP_ATTR_MRU]			= { .type = NLA_U16, },
912309795f4SJames Chapman 	[L2TP_ATTR_STATS]		= { .type = NLA_NESTED, },
913f9bac8dfSChris Elston 	[L2TP_ATTR_IP6_SADDR] = {
914f9bac8dfSChris Elston 		.type = NLA_BINARY,
915f9bac8dfSChris Elston 		.len = sizeof(struct in6_addr),
916f9bac8dfSChris Elston 	},
917f9bac8dfSChris Elston 	[L2TP_ATTR_IP6_DADDR] = {
918f9bac8dfSChris Elston 		.type = NLA_BINARY,
919f9bac8dfSChris Elston 		.len = sizeof(struct in6_addr),
920f9bac8dfSChris Elston 	},
921309795f4SJames Chapman 	[L2TP_ATTR_IFNAME] = {
922309795f4SJames Chapman 		.type = NLA_NUL_STRING,
923309795f4SJames Chapman 		.len = IFNAMSIZ - 1,
924309795f4SJames Chapman 	},
925309795f4SJames Chapman 	[L2TP_ATTR_COOKIE] = {
926309795f4SJames Chapman 		.type = NLA_BINARY,
927309795f4SJames Chapman 		.len = 8,
928309795f4SJames Chapman 	},
929309795f4SJames Chapman 	[L2TP_ATTR_PEER_COOKIE] = {
930309795f4SJames Chapman 		.type = NLA_BINARY,
931309795f4SJames Chapman 		.len = 8,
932309795f4SJames Chapman 	},
933309795f4SJames Chapman };
934309795f4SJames Chapman 
93566a9b928SJakub Kicinski static const struct genl_small_ops l2tp_nl_ops[] = {
936309795f4SJames Chapman 	{
937309795f4SJames Chapman 		.cmd = L2TP_CMD_NOOP,
938ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
939309795f4SJames Chapman 		.doit = l2tp_nl_cmd_noop,
940309795f4SJames Chapman 		/* can be retrieved by unprivileged users */
941309795f4SJames Chapman 	},
942309795f4SJames Chapman 	{
943309795f4SJames Chapman 		.cmd = L2TP_CMD_TUNNEL_CREATE,
944ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
945309795f4SJames Chapman 		.doit = l2tp_nl_cmd_tunnel_create,
9462abe0523SMichael Weiß 		.flags = GENL_UNS_ADMIN_PERM,
947309795f4SJames Chapman 	},
948309795f4SJames Chapman 	{
949309795f4SJames Chapman 		.cmd = L2TP_CMD_TUNNEL_DELETE,
950ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
951309795f4SJames Chapman 		.doit = l2tp_nl_cmd_tunnel_delete,
9522abe0523SMichael Weiß 		.flags = GENL_UNS_ADMIN_PERM,
953309795f4SJames Chapman 	},
954309795f4SJames Chapman 	{
955309795f4SJames Chapman 		.cmd = L2TP_CMD_TUNNEL_MODIFY,
956ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
957309795f4SJames Chapman 		.doit = l2tp_nl_cmd_tunnel_modify,
9582abe0523SMichael Weiß 		.flags = GENL_UNS_ADMIN_PERM,
959309795f4SJames Chapman 	},
960309795f4SJames Chapman 	{
961309795f4SJames Chapman 		.cmd = L2TP_CMD_TUNNEL_GET,
962ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
963309795f4SJames Chapman 		.doit = l2tp_nl_cmd_tunnel_get,
964309795f4SJames Chapman 		.dumpit = l2tp_nl_cmd_tunnel_dump,
9652abe0523SMichael Weiß 		.flags = GENL_UNS_ADMIN_PERM,
966309795f4SJames Chapman 	},
967309795f4SJames Chapman 	{
968309795f4SJames Chapman 		.cmd = L2TP_CMD_SESSION_CREATE,
969ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
970309795f4SJames Chapman 		.doit = l2tp_nl_cmd_session_create,
9712abe0523SMichael Weiß 		.flags = GENL_UNS_ADMIN_PERM,
972309795f4SJames Chapman 	},
973309795f4SJames Chapman 	{
974309795f4SJames Chapman 		.cmd = L2TP_CMD_SESSION_DELETE,
975ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
976309795f4SJames Chapman 		.doit = l2tp_nl_cmd_session_delete,
9772abe0523SMichael Weiß 		.flags = GENL_UNS_ADMIN_PERM,
978309795f4SJames Chapman 	},
979309795f4SJames Chapman 	{
980309795f4SJames Chapman 		.cmd = L2TP_CMD_SESSION_MODIFY,
981ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
982309795f4SJames Chapman 		.doit = l2tp_nl_cmd_session_modify,
9832abe0523SMichael Weiß 		.flags = GENL_UNS_ADMIN_PERM,
984309795f4SJames Chapman 	},
985309795f4SJames Chapman 	{
986309795f4SJames Chapman 		.cmd = L2TP_CMD_SESSION_GET,
987ef6243acSJohannes Berg 		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
988309795f4SJames Chapman 		.doit = l2tp_nl_cmd_session_get,
989309795f4SJames Chapman 		.dumpit = l2tp_nl_cmd_session_dump,
9902abe0523SMichael Weiß 		.flags = GENL_UNS_ADMIN_PERM,
991309795f4SJames Chapman 	},
992309795f4SJames Chapman };
993309795f4SJames Chapman 
99456989f6dSJohannes Berg static struct genl_family l2tp_nl_family __ro_after_init = {
995489111e5SJohannes Berg 	.name		= L2TP_GENL_NAME,
996489111e5SJohannes Berg 	.version	= L2TP_GENL_VERSION,
997489111e5SJohannes Berg 	.hdrsize	= 0,
998489111e5SJohannes Berg 	.maxattr	= L2TP_ATTR_MAX,
9993b0f31f2SJohannes Berg 	.policy = l2tp_nl_policy,
1000489111e5SJohannes Berg 	.netnsok	= true,
1001489111e5SJohannes Berg 	.module		= THIS_MODULE,
100266a9b928SJakub Kicinski 	.small_ops	= l2tp_nl_ops,
100366a9b928SJakub Kicinski 	.n_small_ops	= ARRAY_SIZE(l2tp_nl_ops),
10049c5d03d3SJakub Kicinski 	.resv_start_op	= L2TP_CMD_SESSION_GET + 1,
1005489111e5SJohannes Berg 	.mcgrps		= l2tp_multicast_group,
1006489111e5SJohannes Berg 	.n_mcgrps	= ARRAY_SIZE(l2tp_multicast_group),
1007489111e5SJohannes Berg };
1008489111e5SJohannes Berg 
l2tp_nl_register_ops(enum l2tp_pwtype pw_type,const struct l2tp_nl_cmd_ops * ops)1009309795f4SJames Chapman int l2tp_nl_register_ops(enum l2tp_pwtype pw_type, const struct l2tp_nl_cmd_ops *ops)
1010309795f4SJames Chapman {
1011309795f4SJames Chapman 	int ret;
1012309795f4SJames Chapman 
1013309795f4SJames Chapman 	ret = -EINVAL;
1014309795f4SJames Chapman 	if (pw_type >= __L2TP_PWTYPE_MAX)
1015309795f4SJames Chapman 		goto err;
1016309795f4SJames Chapman 
1017309795f4SJames Chapman 	genl_lock();
1018309795f4SJames Chapman 	ret = -EBUSY;
1019309795f4SJames Chapman 	if (l2tp_nl_cmd_ops[pw_type])
1020309795f4SJames Chapman 		goto out;
1021309795f4SJames Chapman 
1022309795f4SJames Chapman 	l2tp_nl_cmd_ops[pw_type] = ops;
10238cb49014SDavid S. Miller 	ret = 0;
1024309795f4SJames Chapman 
1025309795f4SJames Chapman out:
1026309795f4SJames Chapman 	genl_unlock();
1027309795f4SJames Chapman err:
10288cb49014SDavid S. Miller 	return ret;
1029309795f4SJames Chapman }
1030309795f4SJames Chapman EXPORT_SYMBOL_GPL(l2tp_nl_register_ops);
1031309795f4SJames Chapman 
l2tp_nl_unregister_ops(enum l2tp_pwtype pw_type)1032309795f4SJames Chapman void l2tp_nl_unregister_ops(enum l2tp_pwtype pw_type)
1033309795f4SJames Chapman {
1034309795f4SJames Chapman 	if (pw_type < __L2TP_PWTYPE_MAX) {
1035309795f4SJames Chapman 		genl_lock();
1036309795f4SJames Chapman 		l2tp_nl_cmd_ops[pw_type] = NULL;
1037309795f4SJames Chapman 		genl_unlock();
1038309795f4SJames Chapman 	}
1039309795f4SJames Chapman }
1040309795f4SJames Chapman EXPORT_SYMBOL_GPL(l2tp_nl_unregister_ops);
1041309795f4SJames Chapman 
l2tp_nl_init(void)104256989f6dSJohannes Berg static int __init l2tp_nl_init(void)
1043309795f4SJames Chapman {
1044a4ca44faSJoe Perches 	pr_info("L2TP netlink interface\n");
1045489111e5SJohannes Berg 	return genl_register_family(&l2tp_nl_family);
1046309795f4SJames Chapman }
1047309795f4SJames Chapman 
l2tp_nl_cleanup(void)1048309795f4SJames Chapman static void l2tp_nl_cleanup(void)
1049309795f4SJames Chapman {
1050309795f4SJames Chapman 	genl_unregister_family(&l2tp_nl_family);
1051309795f4SJames Chapman }
1052309795f4SJames Chapman 
1053309795f4SJames Chapman module_init(l2tp_nl_init);
1054309795f4SJames Chapman module_exit(l2tp_nl_cleanup);
1055309795f4SJames Chapman 
1056309795f4SJames Chapman MODULE_AUTHOR("James Chapman <jchapman@katalix.com>");
1057309795f4SJames Chapman MODULE_DESCRIPTION("L2TP netlink");
1058309795f4SJames Chapman MODULE_LICENSE("GPL");
1059309795f4SJames Chapman MODULE_VERSION("1.0");
1060e9412c37SNeil Horman MODULE_ALIAS_GENL_FAMILY("l2tp");
1061