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