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, 40309795f4SJames Chapman }; 41309795f4SJames Chapman 42309795f4SJames Chapman /* Accessed under genl lock */ 43309795f4SJames Chapman static const struct l2tp_nl_cmd_ops *l2tp_nl_cmd_ops[__L2TP_PWTYPE_MAX]; 44309795f4SJames Chapman 45309795f4SJames Chapman static struct l2tp_session *l2tp_nl_session_find(struct genl_info *info) 46309795f4SJames Chapman { 47309795f4SJames Chapman u32 tunnel_id; 48309795f4SJames Chapman u32 session_id; 49309795f4SJames Chapman char *ifname; 50309795f4SJames Chapman struct l2tp_tunnel *tunnel; 51309795f4SJames Chapman struct l2tp_session *session = NULL; 52309795f4SJames Chapman struct net *net = genl_info_net(info); 53309795f4SJames Chapman 54309795f4SJames Chapman if (info->attrs[L2TP_ATTR_IFNAME]) { 55309795f4SJames Chapman ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]); 56309795f4SJames Chapman session = l2tp_session_find_by_ifname(net, ifname); 57309795f4SJames Chapman } else if ((info->attrs[L2TP_ATTR_SESSION_ID]) && 58309795f4SJames Chapman (info->attrs[L2TP_ATTR_CONN_ID])) { 59309795f4SJames Chapman tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); 60309795f4SJames Chapman session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]); 61309795f4SJames Chapman tunnel = l2tp_tunnel_find(net, tunnel_id); 62309795f4SJames Chapman if (tunnel) 63309795f4SJames Chapman session = l2tp_session_find(net, tunnel, session_id); 64309795f4SJames Chapman } 65309795f4SJames Chapman 66309795f4SJames Chapman return session; 67309795f4SJames Chapman } 68309795f4SJames Chapman 69309795f4SJames Chapman static int l2tp_nl_cmd_noop(struct sk_buff *skb, struct genl_info *info) 70309795f4SJames Chapman { 71309795f4SJames Chapman struct sk_buff *msg; 72309795f4SJames Chapman void *hdr; 73309795f4SJames Chapman int ret = -ENOBUFS; 74309795f4SJames Chapman 75*58050fceSThomas Graf msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); 76309795f4SJames Chapman if (!msg) { 77309795f4SJames Chapman ret = -ENOMEM; 78309795f4SJames Chapman goto out; 79309795f4SJames Chapman } 80309795f4SJames Chapman 81309795f4SJames Chapman hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, 82309795f4SJames Chapman &l2tp_nl_family, 0, L2TP_CMD_NOOP); 83309795f4SJames Chapman if (IS_ERR(hdr)) { 84309795f4SJames Chapman ret = PTR_ERR(hdr); 85309795f4SJames Chapman goto err_out; 86309795f4SJames Chapman } 87309795f4SJames Chapman 88309795f4SJames Chapman genlmsg_end(msg, hdr); 89309795f4SJames Chapman 90309795f4SJames Chapman return genlmsg_unicast(genl_info_net(info), msg, info->snd_pid); 91309795f4SJames Chapman 92309795f4SJames Chapman err_out: 93309795f4SJames Chapman nlmsg_free(msg); 94309795f4SJames Chapman 95309795f4SJames Chapman out: 96309795f4SJames Chapman return ret; 97309795f4SJames Chapman } 98309795f4SJames Chapman 99309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info) 100309795f4SJames Chapman { 101309795f4SJames Chapman u32 tunnel_id; 102309795f4SJames Chapman u32 peer_tunnel_id; 103309795f4SJames Chapman int proto_version; 104309795f4SJames Chapman int fd; 105309795f4SJames Chapman int ret = 0; 106309795f4SJames Chapman struct l2tp_tunnel_cfg cfg = { 0, }; 107309795f4SJames Chapman struct l2tp_tunnel *tunnel; 108309795f4SJames Chapman struct net *net = genl_info_net(info); 109309795f4SJames Chapman 110309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_CONN_ID]) { 111309795f4SJames Chapman ret = -EINVAL; 112309795f4SJames Chapman goto out; 113309795f4SJames Chapman } 114309795f4SJames Chapman tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); 115309795f4SJames Chapman 116309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_PEER_CONN_ID]) { 117309795f4SJames Chapman ret = -EINVAL; 118309795f4SJames Chapman goto out; 119309795f4SJames Chapman } 120309795f4SJames Chapman peer_tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_PEER_CONN_ID]); 121309795f4SJames Chapman 122309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_PROTO_VERSION]) { 123309795f4SJames Chapman ret = -EINVAL; 124309795f4SJames Chapman goto out; 125309795f4SJames Chapman } 126309795f4SJames Chapman proto_version = nla_get_u8(info->attrs[L2TP_ATTR_PROTO_VERSION]); 127309795f4SJames Chapman 128309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_ENCAP_TYPE]) { 129309795f4SJames Chapman ret = -EINVAL; 130309795f4SJames Chapman goto out; 131309795f4SJames Chapman } 132309795f4SJames Chapman cfg.encap = nla_get_u16(info->attrs[L2TP_ATTR_ENCAP_TYPE]); 133309795f4SJames Chapman 134789a4a2cSJames Chapman fd = -1; 135789a4a2cSJames Chapman if (info->attrs[L2TP_ATTR_FD]) { 136309795f4SJames Chapman fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]); 137789a4a2cSJames Chapman } else { 138f9bac8dfSChris Elston #if IS_ENABLED(CONFIG_IPV6) 139f9bac8dfSChris Elston if (info->attrs[L2TP_ATTR_IP6_SADDR] && 140f9bac8dfSChris Elston info->attrs[L2TP_ATTR_IP6_DADDR]) { 141f9bac8dfSChris Elston cfg.local_ip6 = nla_data( 142f9bac8dfSChris Elston info->attrs[L2TP_ATTR_IP6_SADDR]); 143f9bac8dfSChris Elston cfg.peer_ip6 = nla_data( 144f9bac8dfSChris Elston info->attrs[L2TP_ATTR_IP6_DADDR]); 145f9bac8dfSChris Elston } else 146f9bac8dfSChris Elston #endif 147f9bac8dfSChris Elston if (info->attrs[L2TP_ATTR_IP_SADDR] && 148f9bac8dfSChris Elston info->attrs[L2TP_ATTR_IP_DADDR]) { 149f9bac8dfSChris Elston cfg.local_ip.s_addr = nla_get_be32( 150f9bac8dfSChris Elston info->attrs[L2TP_ATTR_IP_SADDR]); 151f9bac8dfSChris Elston cfg.peer_ip.s_addr = nla_get_be32( 152f9bac8dfSChris Elston info->attrs[L2TP_ATTR_IP_DADDR]); 153f9bac8dfSChris Elston } else { 154f9bac8dfSChris Elston ret = -EINVAL; 155f9bac8dfSChris Elston goto out; 156f9bac8dfSChris Elston } 157789a4a2cSJames Chapman if (info->attrs[L2TP_ATTR_UDP_SPORT]) 158789a4a2cSJames Chapman cfg.local_udp_port = nla_get_u16(info->attrs[L2TP_ATTR_UDP_SPORT]); 159789a4a2cSJames Chapman if (info->attrs[L2TP_ATTR_UDP_DPORT]) 160789a4a2cSJames Chapman cfg.peer_udp_port = nla_get_u16(info->attrs[L2TP_ATTR_UDP_DPORT]); 161789a4a2cSJames Chapman if (info->attrs[L2TP_ATTR_UDP_CSUM]) 162789a4a2cSJames Chapman cfg.use_udp_checksums = nla_get_flag(info->attrs[L2TP_ATTR_UDP_CSUM]); 163789a4a2cSJames Chapman } 164309795f4SJames Chapman 165309795f4SJames Chapman if (info->attrs[L2TP_ATTR_DEBUG]) 166309795f4SJames Chapman cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); 167309795f4SJames Chapman 168309795f4SJames Chapman tunnel = l2tp_tunnel_find(net, tunnel_id); 169309795f4SJames Chapman if (tunnel != NULL) { 170309795f4SJames Chapman ret = -EEXIST; 171309795f4SJames Chapman goto out; 172309795f4SJames Chapman } 173309795f4SJames Chapman 174309795f4SJames Chapman ret = -EINVAL; 175309795f4SJames Chapman switch (cfg.encap) { 176309795f4SJames Chapman case L2TP_ENCAPTYPE_UDP: 177309795f4SJames Chapman case L2TP_ENCAPTYPE_IP: 178309795f4SJames Chapman ret = l2tp_tunnel_create(net, fd, proto_version, tunnel_id, 179309795f4SJames Chapman peer_tunnel_id, &cfg, &tunnel); 180309795f4SJames Chapman break; 181309795f4SJames Chapman } 182309795f4SJames Chapman 183309795f4SJames Chapman out: 184309795f4SJames Chapman return ret; 185309795f4SJames Chapman } 186309795f4SJames Chapman 187309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_delete(struct sk_buff *skb, struct genl_info *info) 188309795f4SJames Chapman { 189309795f4SJames Chapman struct l2tp_tunnel *tunnel; 190309795f4SJames Chapman u32 tunnel_id; 191309795f4SJames Chapman int ret = 0; 192309795f4SJames Chapman struct net *net = genl_info_net(info); 193309795f4SJames Chapman 194309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_CONN_ID]) { 195309795f4SJames Chapman ret = -EINVAL; 196309795f4SJames Chapman goto out; 197309795f4SJames Chapman } 198309795f4SJames Chapman tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); 199309795f4SJames Chapman 200309795f4SJames Chapman tunnel = l2tp_tunnel_find(net, tunnel_id); 201309795f4SJames Chapman if (tunnel == NULL) { 202309795f4SJames Chapman ret = -ENODEV; 203309795f4SJames Chapman goto out; 204309795f4SJames Chapman } 205309795f4SJames Chapman 206309795f4SJames Chapman (void) l2tp_tunnel_delete(tunnel); 207309795f4SJames Chapman 208309795f4SJames Chapman out: 209309795f4SJames Chapman return ret; 210309795f4SJames Chapman } 211309795f4SJames Chapman 212309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_modify(struct sk_buff *skb, struct genl_info *info) 213309795f4SJames Chapman { 214309795f4SJames Chapman struct l2tp_tunnel *tunnel; 215309795f4SJames Chapman u32 tunnel_id; 216309795f4SJames Chapman int ret = 0; 217309795f4SJames Chapman struct net *net = genl_info_net(info); 218309795f4SJames Chapman 219309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_CONN_ID]) { 220309795f4SJames Chapman ret = -EINVAL; 221309795f4SJames Chapman goto out; 222309795f4SJames Chapman } 223309795f4SJames Chapman tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); 224309795f4SJames Chapman 225309795f4SJames Chapman tunnel = l2tp_tunnel_find(net, tunnel_id); 226309795f4SJames Chapman if (tunnel == NULL) { 227309795f4SJames Chapman ret = -ENODEV; 228309795f4SJames Chapman goto out; 229309795f4SJames Chapman } 230309795f4SJames Chapman 231309795f4SJames Chapman if (info->attrs[L2TP_ATTR_DEBUG]) 232309795f4SJames Chapman tunnel->debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); 233309795f4SJames Chapman 234309795f4SJames Chapman out: 235309795f4SJames Chapman return ret; 236309795f4SJames Chapman } 237309795f4SJames Chapman 238309795f4SJames Chapman static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 pid, u32 seq, int flags, 239309795f4SJames Chapman struct l2tp_tunnel *tunnel) 240309795f4SJames Chapman { 241309795f4SJames Chapman void *hdr; 242309795f4SJames Chapman struct nlattr *nest; 243309795f4SJames Chapman struct sock *sk = NULL; 244309795f4SJames Chapman struct inet_sock *inet; 245f9bac8dfSChris Elston #if IS_ENABLED(CONFIG_IPV6) 246f9bac8dfSChris Elston struct ipv6_pinfo *np = NULL; 247f9bac8dfSChris Elston #endif 2485de7aee5SJames Chapman struct l2tp_stats stats; 2495de7aee5SJames Chapman unsigned int start; 250309795f4SJames Chapman 251309795f4SJames Chapman hdr = genlmsg_put(skb, pid, seq, &l2tp_nl_family, flags, 252309795f4SJames Chapman L2TP_CMD_TUNNEL_GET); 253309795f4SJames Chapman if (IS_ERR(hdr)) 254309795f4SJames Chapman return PTR_ERR(hdr); 255309795f4SJames Chapman 25660aed2abSDavid S. Miller if (nla_put_u8(skb, L2TP_ATTR_PROTO_VERSION, tunnel->version) || 25760aed2abSDavid S. Miller nla_put_u32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id) || 25860aed2abSDavid S. Miller nla_put_u32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id) || 25960aed2abSDavid S. Miller nla_put_u32(skb, L2TP_ATTR_DEBUG, tunnel->debug) || 26060aed2abSDavid S. Miller nla_put_u16(skb, L2TP_ATTR_ENCAP_TYPE, tunnel->encap)) 26160aed2abSDavid S. Miller goto nla_put_failure; 262309795f4SJames Chapman 263309795f4SJames Chapman nest = nla_nest_start(skb, L2TP_ATTR_STATS); 264309795f4SJames Chapman if (nest == NULL) 265309795f4SJames Chapman goto nla_put_failure; 266309795f4SJames Chapman 2675de7aee5SJames Chapman do { 2685de7aee5SJames Chapman start = u64_stats_fetch_begin(&tunnel->stats.syncp); 2695de7aee5SJames Chapman stats.tx_packets = tunnel->stats.tx_packets; 2705de7aee5SJames Chapman stats.tx_bytes = tunnel->stats.tx_bytes; 2715de7aee5SJames Chapman stats.tx_errors = tunnel->stats.tx_errors; 2725de7aee5SJames Chapman stats.rx_packets = tunnel->stats.rx_packets; 2735de7aee5SJames Chapman stats.rx_bytes = tunnel->stats.rx_bytes; 2745de7aee5SJames Chapman stats.rx_errors = tunnel->stats.rx_errors; 2755de7aee5SJames Chapman stats.rx_seq_discards = tunnel->stats.rx_seq_discards; 2765de7aee5SJames Chapman stats.rx_oos_packets = tunnel->stats.rx_oos_packets; 2775de7aee5SJames Chapman } while (u64_stats_fetch_retry(&tunnel->stats.syncp, start)); 2785de7aee5SJames Chapman 2795de7aee5SJames Chapman if (nla_put_u64(skb, L2TP_ATTR_TX_PACKETS, stats.tx_packets) || 2805de7aee5SJames Chapman nla_put_u64(skb, L2TP_ATTR_TX_BYTES, stats.tx_bytes) || 2815de7aee5SJames Chapman nla_put_u64(skb, L2TP_ATTR_TX_ERRORS, stats.tx_errors) || 2825de7aee5SJames Chapman nla_put_u64(skb, L2TP_ATTR_RX_PACKETS, stats.rx_packets) || 2835de7aee5SJames Chapman nla_put_u64(skb, L2TP_ATTR_RX_BYTES, stats.rx_bytes) || 28460aed2abSDavid S. Miller nla_put_u64(skb, L2TP_ATTR_RX_SEQ_DISCARDS, 2855de7aee5SJames Chapman stats.rx_seq_discards) || 28660aed2abSDavid S. Miller nla_put_u64(skb, L2TP_ATTR_RX_OOS_PACKETS, 2875de7aee5SJames Chapman stats.rx_oos_packets) || 2885de7aee5SJames Chapman nla_put_u64(skb, L2TP_ATTR_RX_ERRORS, stats.rx_errors)) 28960aed2abSDavid S. Miller goto nla_put_failure; 290309795f4SJames Chapman nla_nest_end(skb, nest); 291309795f4SJames Chapman 292309795f4SJames Chapman sk = tunnel->sock; 293309795f4SJames Chapman if (!sk) 294309795f4SJames Chapman goto out; 295309795f4SJames Chapman 296f9bac8dfSChris Elston #if IS_ENABLED(CONFIG_IPV6) 297f9bac8dfSChris Elston if (sk->sk_family == AF_INET6) 298f9bac8dfSChris Elston np = inet6_sk(sk); 299f9bac8dfSChris Elston #endif 300f9bac8dfSChris Elston 301309795f4SJames Chapman inet = inet_sk(sk); 302309795f4SJames Chapman 303309795f4SJames Chapman switch (tunnel->encap) { 304309795f4SJames Chapman case L2TP_ENCAPTYPE_UDP: 30560aed2abSDavid S. Miller if (nla_put_u16(skb, L2TP_ATTR_UDP_SPORT, ntohs(inet->inet_sport)) || 30660aed2abSDavid S. Miller nla_put_u16(skb, L2TP_ATTR_UDP_DPORT, ntohs(inet->inet_dport)) || 30760aed2abSDavid S. Miller nla_put_u8(skb, L2TP_ATTR_UDP_CSUM, 30860aed2abSDavid S. Miller (sk->sk_no_check != UDP_CSUM_NOXMIT))) 30960aed2abSDavid S. Miller goto nla_put_failure; 310309795f4SJames Chapman /* NOBREAK */ 311309795f4SJames Chapman case L2TP_ENCAPTYPE_IP: 312f9bac8dfSChris Elston #if IS_ENABLED(CONFIG_IPV6) 313f9bac8dfSChris Elston if (np) { 314f9bac8dfSChris Elston if (nla_put(skb, L2TP_ATTR_IP6_SADDR, sizeof(np->saddr), 315f9bac8dfSChris Elston &np->saddr) || 316f9bac8dfSChris Elston nla_put(skb, L2TP_ATTR_IP6_DADDR, sizeof(np->daddr), 317f9bac8dfSChris Elston &np->daddr)) 318f9bac8dfSChris Elston goto nla_put_failure; 319f9bac8dfSChris Elston } else 320f9bac8dfSChris Elston #endif 32160aed2abSDavid S. Miller if (nla_put_be32(skb, L2TP_ATTR_IP_SADDR, inet->inet_saddr) || 32260aed2abSDavid S. Miller nla_put_be32(skb, L2TP_ATTR_IP_DADDR, inet->inet_daddr)) 32360aed2abSDavid S. Miller goto nla_put_failure; 324309795f4SJames Chapman break; 325309795f4SJames Chapman } 326309795f4SJames Chapman 327309795f4SJames Chapman out: 328309795f4SJames Chapman return genlmsg_end(skb, hdr); 329309795f4SJames Chapman 330309795f4SJames Chapman nla_put_failure: 331309795f4SJames Chapman genlmsg_cancel(skb, hdr); 332309795f4SJames Chapman return -1; 333309795f4SJames Chapman } 334309795f4SJames Chapman 335309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_get(struct sk_buff *skb, struct genl_info *info) 336309795f4SJames Chapman { 337309795f4SJames Chapman struct l2tp_tunnel *tunnel; 338309795f4SJames Chapman struct sk_buff *msg; 339309795f4SJames Chapman u32 tunnel_id; 340309795f4SJames Chapman int ret = -ENOBUFS; 341309795f4SJames Chapman struct net *net = genl_info_net(info); 342309795f4SJames Chapman 343309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_CONN_ID]) { 344309795f4SJames Chapman ret = -EINVAL; 345309795f4SJames Chapman goto out; 346309795f4SJames Chapman } 347309795f4SJames Chapman 348309795f4SJames Chapman tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); 349309795f4SJames Chapman 350309795f4SJames Chapman tunnel = l2tp_tunnel_find(net, tunnel_id); 351309795f4SJames Chapman if (tunnel == NULL) { 352309795f4SJames Chapman ret = -ENODEV; 353309795f4SJames Chapman goto out; 354309795f4SJames Chapman } 355309795f4SJames Chapman 356*58050fceSThomas Graf msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); 357309795f4SJames Chapman if (!msg) { 358309795f4SJames Chapman ret = -ENOMEM; 359309795f4SJames Chapman goto out; 360309795f4SJames Chapman } 361309795f4SJames Chapman 362309795f4SJames Chapman ret = l2tp_nl_tunnel_send(msg, info->snd_pid, info->snd_seq, 363309795f4SJames Chapman NLM_F_ACK, tunnel); 364309795f4SJames Chapman if (ret < 0) 365309795f4SJames Chapman goto err_out; 366309795f4SJames Chapman 367309795f4SJames Chapman return genlmsg_unicast(net, msg, info->snd_pid); 368309795f4SJames Chapman 369309795f4SJames Chapman err_out: 370309795f4SJames Chapman nlmsg_free(msg); 371309795f4SJames Chapman 372309795f4SJames Chapman out: 373309795f4SJames Chapman return ret; 374309795f4SJames Chapman } 375309795f4SJames Chapman 376309795f4SJames Chapman static int l2tp_nl_cmd_tunnel_dump(struct sk_buff *skb, struct netlink_callback *cb) 377309795f4SJames Chapman { 378309795f4SJames Chapman int ti = cb->args[0]; 379309795f4SJames Chapman struct l2tp_tunnel *tunnel; 380309795f4SJames Chapman struct net *net = sock_net(skb->sk); 381309795f4SJames Chapman 382309795f4SJames Chapman for (;;) { 383309795f4SJames Chapman tunnel = l2tp_tunnel_find_nth(net, ti); 384309795f4SJames Chapman if (tunnel == NULL) 385309795f4SJames Chapman goto out; 386309795f4SJames Chapman 387309795f4SJames Chapman if (l2tp_nl_tunnel_send(skb, NETLINK_CB(cb->skb).pid, 388309795f4SJames Chapman cb->nlh->nlmsg_seq, NLM_F_MULTI, 389309795f4SJames Chapman tunnel) <= 0) 390309795f4SJames Chapman goto out; 391309795f4SJames Chapman 392309795f4SJames Chapman ti++; 393309795f4SJames Chapman } 394309795f4SJames Chapman 395309795f4SJames Chapman out: 396309795f4SJames Chapman cb->args[0] = ti; 397309795f4SJames Chapman 398309795f4SJames Chapman return skb->len; 399309795f4SJames Chapman } 400309795f4SJames Chapman 401309795f4SJames Chapman static int l2tp_nl_cmd_session_create(struct sk_buff *skb, struct genl_info *info) 402309795f4SJames Chapman { 403309795f4SJames Chapman u32 tunnel_id = 0; 404309795f4SJames Chapman u32 session_id; 405309795f4SJames Chapman u32 peer_session_id; 406309795f4SJames Chapman int ret = 0; 407309795f4SJames Chapman struct l2tp_tunnel *tunnel; 408309795f4SJames Chapman struct l2tp_session *session; 409309795f4SJames Chapman struct l2tp_session_cfg cfg = { 0, }; 410309795f4SJames Chapman struct net *net = genl_info_net(info); 411309795f4SJames Chapman 412309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_CONN_ID]) { 413309795f4SJames Chapman ret = -EINVAL; 414309795f4SJames Chapman goto out; 415309795f4SJames Chapman } 416309795f4SJames Chapman tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]); 417309795f4SJames Chapman tunnel = l2tp_tunnel_find(net, tunnel_id); 418309795f4SJames Chapman if (!tunnel) { 419309795f4SJames Chapman ret = -ENODEV; 420309795f4SJames Chapman goto out; 421309795f4SJames Chapman } 422309795f4SJames Chapman 423309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_SESSION_ID]) { 424309795f4SJames Chapman ret = -EINVAL; 425309795f4SJames Chapman goto out; 426309795f4SJames Chapman } 427309795f4SJames Chapman session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]); 428309795f4SJames Chapman session = l2tp_session_find(net, tunnel, session_id); 429309795f4SJames Chapman if (session) { 430309795f4SJames Chapman ret = -EEXIST; 431309795f4SJames Chapman goto out; 432309795f4SJames Chapman } 433309795f4SJames Chapman 434309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_PEER_SESSION_ID]) { 435309795f4SJames Chapman ret = -EINVAL; 436309795f4SJames Chapman goto out; 437309795f4SJames Chapman } 438309795f4SJames Chapman peer_session_id = nla_get_u32(info->attrs[L2TP_ATTR_PEER_SESSION_ID]); 439309795f4SJames Chapman 440309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_PW_TYPE]) { 441309795f4SJames Chapman ret = -EINVAL; 442309795f4SJames Chapman goto out; 443309795f4SJames Chapman } 444309795f4SJames Chapman cfg.pw_type = nla_get_u16(info->attrs[L2TP_ATTR_PW_TYPE]); 445309795f4SJames Chapman if (cfg.pw_type >= __L2TP_PWTYPE_MAX) { 446309795f4SJames Chapman ret = -EINVAL; 447309795f4SJames Chapman goto out; 448309795f4SJames Chapman } 449309795f4SJames Chapman 450309795f4SJames Chapman if (tunnel->version > 2) { 451309795f4SJames Chapman if (info->attrs[L2TP_ATTR_OFFSET]) 452309795f4SJames Chapman cfg.offset = nla_get_u16(info->attrs[L2TP_ATTR_OFFSET]); 453309795f4SJames Chapman 454309795f4SJames Chapman if (info->attrs[L2TP_ATTR_DATA_SEQ]) 455309795f4SJames Chapman cfg.data_seq = nla_get_u8(info->attrs[L2TP_ATTR_DATA_SEQ]); 456309795f4SJames Chapman 457309795f4SJames Chapman cfg.l2specific_type = L2TP_L2SPECTYPE_DEFAULT; 458309795f4SJames Chapman if (info->attrs[L2TP_ATTR_L2SPEC_TYPE]) 459309795f4SJames Chapman cfg.l2specific_type = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_TYPE]); 460309795f4SJames Chapman 461309795f4SJames Chapman cfg.l2specific_len = 4; 462309795f4SJames Chapman if (info->attrs[L2TP_ATTR_L2SPEC_LEN]) 463309795f4SJames Chapman cfg.l2specific_len = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_LEN]); 464309795f4SJames Chapman 465309795f4SJames Chapman if (info->attrs[L2TP_ATTR_COOKIE]) { 466309795f4SJames Chapman u16 len = nla_len(info->attrs[L2TP_ATTR_COOKIE]); 467309795f4SJames Chapman if (len > 8) { 468309795f4SJames Chapman ret = -EINVAL; 469309795f4SJames Chapman goto out; 470309795f4SJames Chapman } 471309795f4SJames Chapman cfg.cookie_len = len; 472309795f4SJames Chapman memcpy(&cfg.cookie[0], nla_data(info->attrs[L2TP_ATTR_COOKIE]), len); 473309795f4SJames Chapman } 474309795f4SJames Chapman if (info->attrs[L2TP_ATTR_PEER_COOKIE]) { 475309795f4SJames Chapman u16 len = nla_len(info->attrs[L2TP_ATTR_PEER_COOKIE]); 476309795f4SJames Chapman if (len > 8) { 477309795f4SJames Chapman ret = -EINVAL; 478309795f4SJames Chapman goto out; 479309795f4SJames Chapman } 480309795f4SJames Chapman cfg.peer_cookie_len = len; 481309795f4SJames Chapman memcpy(&cfg.peer_cookie[0], nla_data(info->attrs[L2TP_ATTR_PEER_COOKIE]), len); 482309795f4SJames Chapman } 483309795f4SJames Chapman if (info->attrs[L2TP_ATTR_IFNAME]) 484309795f4SJames Chapman cfg.ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]); 485309795f4SJames Chapman 486309795f4SJames Chapman if (info->attrs[L2TP_ATTR_VLAN_ID]) 487309795f4SJames Chapman cfg.vlan_id = nla_get_u16(info->attrs[L2TP_ATTR_VLAN_ID]); 488309795f4SJames Chapman } 489309795f4SJames Chapman 490309795f4SJames Chapman if (info->attrs[L2TP_ATTR_DEBUG]) 491309795f4SJames Chapman cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); 492309795f4SJames Chapman 493309795f4SJames Chapman if (info->attrs[L2TP_ATTR_RECV_SEQ]) 494309795f4SJames Chapman cfg.recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]); 495309795f4SJames Chapman 496309795f4SJames Chapman if (info->attrs[L2TP_ATTR_SEND_SEQ]) 497309795f4SJames Chapman cfg.send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]); 498309795f4SJames Chapman 499309795f4SJames Chapman if (info->attrs[L2TP_ATTR_LNS_MODE]) 500309795f4SJames Chapman cfg.lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]); 501309795f4SJames Chapman 502309795f4SJames Chapman if (info->attrs[L2TP_ATTR_RECV_TIMEOUT]) 503309795f4SJames Chapman cfg.reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]); 504309795f4SJames Chapman 505309795f4SJames Chapman if (info->attrs[L2TP_ATTR_MTU]) 506309795f4SJames Chapman cfg.mtu = nla_get_u16(info->attrs[L2TP_ATTR_MTU]); 507309795f4SJames Chapman 508309795f4SJames Chapman if (info->attrs[L2TP_ATTR_MRU]) 509309795f4SJames Chapman cfg.mru = nla_get_u16(info->attrs[L2TP_ATTR_MRU]); 510309795f4SJames Chapman 511309795f4SJames Chapman if ((l2tp_nl_cmd_ops[cfg.pw_type] == NULL) || 512309795f4SJames Chapman (l2tp_nl_cmd_ops[cfg.pw_type]->session_create == NULL)) { 513309795f4SJames Chapman ret = -EPROTONOSUPPORT; 514309795f4SJames Chapman goto out; 515309795f4SJames Chapman } 516309795f4SJames Chapman 517309795f4SJames Chapman /* Check that pseudowire-specific params are present */ 518309795f4SJames Chapman switch (cfg.pw_type) { 519309795f4SJames Chapman case L2TP_PWTYPE_NONE: 520309795f4SJames Chapman break; 521309795f4SJames Chapman case L2TP_PWTYPE_ETH_VLAN: 522309795f4SJames Chapman if (!info->attrs[L2TP_ATTR_VLAN_ID]) { 523309795f4SJames Chapman ret = -EINVAL; 524309795f4SJames Chapman goto out; 525309795f4SJames Chapman } 526309795f4SJames Chapman break; 527309795f4SJames Chapman case L2TP_PWTYPE_ETH: 528309795f4SJames Chapman break; 529309795f4SJames Chapman case L2TP_PWTYPE_PPP: 530309795f4SJames Chapman case L2TP_PWTYPE_PPP_AC: 531309795f4SJames Chapman break; 532309795f4SJames Chapman case L2TP_PWTYPE_IP: 533309795f4SJames Chapman default: 534309795f4SJames Chapman ret = -EPROTONOSUPPORT; 535309795f4SJames Chapman break; 536309795f4SJames Chapman } 537309795f4SJames Chapman 538309795f4SJames Chapman ret = -EPROTONOSUPPORT; 539309795f4SJames Chapman if (l2tp_nl_cmd_ops[cfg.pw_type]->session_create) 540309795f4SJames Chapman ret = (*l2tp_nl_cmd_ops[cfg.pw_type]->session_create)(net, tunnel_id, 541309795f4SJames Chapman session_id, peer_session_id, &cfg); 542309795f4SJames Chapman 543309795f4SJames Chapman out: 544309795f4SJames Chapman return ret; 545309795f4SJames Chapman } 546309795f4SJames Chapman 547309795f4SJames Chapman static int l2tp_nl_cmd_session_delete(struct sk_buff *skb, struct genl_info *info) 548309795f4SJames Chapman { 549309795f4SJames Chapman int ret = 0; 550309795f4SJames Chapman struct l2tp_session *session; 551309795f4SJames Chapman u16 pw_type; 552309795f4SJames Chapman 553309795f4SJames Chapman session = l2tp_nl_session_find(info); 554309795f4SJames Chapman if (session == NULL) { 555309795f4SJames Chapman ret = -ENODEV; 556309795f4SJames Chapman goto out; 557309795f4SJames Chapman } 558309795f4SJames Chapman 559309795f4SJames Chapman pw_type = session->pwtype; 560309795f4SJames Chapman if (pw_type < __L2TP_PWTYPE_MAX) 561309795f4SJames Chapman if (l2tp_nl_cmd_ops[pw_type] && l2tp_nl_cmd_ops[pw_type]->session_delete) 562309795f4SJames Chapman ret = (*l2tp_nl_cmd_ops[pw_type]->session_delete)(session); 563309795f4SJames Chapman 564309795f4SJames Chapman out: 565309795f4SJames Chapman return ret; 566309795f4SJames Chapman } 567309795f4SJames Chapman 568309795f4SJames Chapman static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *info) 569309795f4SJames Chapman { 570309795f4SJames Chapman int ret = 0; 571309795f4SJames Chapman struct l2tp_session *session; 572309795f4SJames Chapman 573309795f4SJames Chapman session = l2tp_nl_session_find(info); 574309795f4SJames Chapman if (session == NULL) { 575309795f4SJames Chapman ret = -ENODEV; 576309795f4SJames Chapman goto out; 577309795f4SJames Chapman } 578309795f4SJames Chapman 579309795f4SJames Chapman if (info->attrs[L2TP_ATTR_DEBUG]) 580309795f4SJames Chapman session->debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]); 581309795f4SJames Chapman 582309795f4SJames Chapman if (info->attrs[L2TP_ATTR_DATA_SEQ]) 583309795f4SJames Chapman session->data_seq = nla_get_u8(info->attrs[L2TP_ATTR_DATA_SEQ]); 584309795f4SJames Chapman 585309795f4SJames Chapman if (info->attrs[L2TP_ATTR_RECV_SEQ]) 586309795f4SJames Chapman session->recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]); 587309795f4SJames Chapman 588309795f4SJames Chapman if (info->attrs[L2TP_ATTR_SEND_SEQ]) 589309795f4SJames Chapman session->send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]); 590309795f4SJames Chapman 591309795f4SJames Chapman if (info->attrs[L2TP_ATTR_LNS_MODE]) 592309795f4SJames Chapman session->lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]); 593309795f4SJames Chapman 594309795f4SJames Chapman if (info->attrs[L2TP_ATTR_RECV_TIMEOUT]) 595309795f4SJames Chapman session->reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]); 596309795f4SJames Chapman 597309795f4SJames Chapman if (info->attrs[L2TP_ATTR_MTU]) 598309795f4SJames Chapman session->mtu = nla_get_u16(info->attrs[L2TP_ATTR_MTU]); 599309795f4SJames Chapman 600309795f4SJames Chapman if (info->attrs[L2TP_ATTR_MRU]) 601309795f4SJames Chapman session->mru = nla_get_u16(info->attrs[L2TP_ATTR_MRU]); 602309795f4SJames Chapman 603309795f4SJames Chapman out: 604309795f4SJames Chapman return ret; 605309795f4SJames Chapman } 606309795f4SJames Chapman 607309795f4SJames Chapman static int l2tp_nl_session_send(struct sk_buff *skb, u32 pid, u32 seq, int flags, 608309795f4SJames Chapman struct l2tp_session *session) 609309795f4SJames Chapman { 610309795f4SJames Chapman void *hdr; 611309795f4SJames Chapman struct nlattr *nest; 612309795f4SJames Chapman struct l2tp_tunnel *tunnel = session->tunnel; 613309795f4SJames Chapman struct sock *sk = NULL; 6145de7aee5SJames Chapman struct l2tp_stats stats; 6155de7aee5SJames Chapman unsigned int start; 616309795f4SJames Chapman 617309795f4SJames Chapman sk = tunnel->sock; 618309795f4SJames Chapman 619309795f4SJames Chapman hdr = genlmsg_put(skb, pid, seq, &l2tp_nl_family, flags, L2TP_CMD_SESSION_GET); 620309795f4SJames Chapman if (IS_ERR(hdr)) 621309795f4SJames Chapman return PTR_ERR(hdr); 622309795f4SJames Chapman 62360aed2abSDavid S. Miller if (nla_put_u32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id) || 62460aed2abSDavid S. Miller nla_put_u32(skb, L2TP_ATTR_SESSION_ID, session->session_id) || 62560aed2abSDavid S. Miller nla_put_u32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id) || 62660aed2abSDavid S. Miller nla_put_u32(skb, L2TP_ATTR_PEER_SESSION_ID, 62760aed2abSDavid S. Miller session->peer_session_id) || 62860aed2abSDavid S. Miller nla_put_u32(skb, L2TP_ATTR_DEBUG, session->debug) || 62960aed2abSDavid S. Miller nla_put_u16(skb, L2TP_ATTR_PW_TYPE, session->pwtype) || 63060aed2abSDavid S. Miller nla_put_u16(skb, L2TP_ATTR_MTU, session->mtu) || 63160aed2abSDavid S. Miller (session->mru && 63260aed2abSDavid S. Miller nla_put_u16(skb, L2TP_ATTR_MRU, session->mru))) 63360aed2abSDavid S. Miller goto nla_put_failure; 634309795f4SJames Chapman 63560aed2abSDavid S. Miller if ((session->ifname && session->ifname[0] && 63660aed2abSDavid S. Miller nla_put_string(skb, L2TP_ATTR_IFNAME, session->ifname)) || 63760aed2abSDavid S. Miller (session->cookie_len && 63860aed2abSDavid S. Miller nla_put(skb, L2TP_ATTR_COOKIE, session->cookie_len, 63960aed2abSDavid S. Miller &session->cookie[0])) || 64060aed2abSDavid S. Miller (session->peer_cookie_len && 64160aed2abSDavid S. Miller nla_put(skb, L2TP_ATTR_PEER_COOKIE, session->peer_cookie_len, 64260aed2abSDavid S. Miller &session->peer_cookie[0])) || 64360aed2abSDavid S. Miller nla_put_u8(skb, L2TP_ATTR_RECV_SEQ, session->recv_seq) || 64460aed2abSDavid S. Miller nla_put_u8(skb, L2TP_ATTR_SEND_SEQ, session->send_seq) || 64560aed2abSDavid S. Miller nla_put_u8(skb, L2TP_ATTR_LNS_MODE, session->lns_mode) || 646309795f4SJames Chapman #ifdef CONFIG_XFRM 64760aed2abSDavid S. Miller (((sk) && (sk->sk_policy[0] || sk->sk_policy[1])) && 64860aed2abSDavid S. Miller nla_put_u8(skb, L2TP_ATTR_USING_IPSEC, 1)) || 649309795f4SJames Chapman #endif 65060aed2abSDavid S. Miller (session->reorder_timeout && 65160aed2abSDavid S. Miller nla_put_msecs(skb, L2TP_ATTR_RECV_TIMEOUT, session->reorder_timeout))) 65260aed2abSDavid S. Miller goto nla_put_failure; 6535de7aee5SJames Chapman 654309795f4SJames Chapman nest = nla_nest_start(skb, L2TP_ATTR_STATS); 655309795f4SJames Chapman if (nest == NULL) 656309795f4SJames Chapman goto nla_put_failure; 6575de7aee5SJames Chapman 6585de7aee5SJames Chapman do { 6595de7aee5SJames Chapman start = u64_stats_fetch_begin(&session->stats.syncp); 6605de7aee5SJames Chapman stats.tx_packets = session->stats.tx_packets; 6615de7aee5SJames Chapman stats.tx_bytes = session->stats.tx_bytes; 6625de7aee5SJames Chapman stats.tx_errors = session->stats.tx_errors; 6635de7aee5SJames Chapman stats.rx_packets = session->stats.rx_packets; 6645de7aee5SJames Chapman stats.rx_bytes = session->stats.rx_bytes; 6655de7aee5SJames Chapman stats.rx_errors = session->stats.rx_errors; 6665de7aee5SJames Chapman stats.rx_seq_discards = session->stats.rx_seq_discards; 6675de7aee5SJames Chapman stats.rx_oos_packets = session->stats.rx_oos_packets; 6685de7aee5SJames Chapman } while (u64_stats_fetch_retry(&session->stats.syncp, start)); 6695de7aee5SJames Chapman 6705de7aee5SJames Chapman if (nla_put_u64(skb, L2TP_ATTR_TX_PACKETS, stats.tx_packets) || 6715de7aee5SJames Chapman nla_put_u64(skb, L2TP_ATTR_TX_BYTES, stats.tx_bytes) || 6725de7aee5SJames Chapman nla_put_u64(skb, L2TP_ATTR_TX_ERRORS, stats.tx_errors) || 6735de7aee5SJames Chapman nla_put_u64(skb, L2TP_ATTR_RX_PACKETS, stats.rx_packets) || 6745de7aee5SJames Chapman nla_put_u64(skb, L2TP_ATTR_RX_BYTES, stats.rx_bytes) || 67560aed2abSDavid S. Miller nla_put_u64(skb, L2TP_ATTR_RX_SEQ_DISCARDS, 6765de7aee5SJames Chapman stats.rx_seq_discards) || 67760aed2abSDavid S. Miller nla_put_u64(skb, L2TP_ATTR_RX_OOS_PACKETS, 6785de7aee5SJames Chapman stats.rx_oos_packets) || 6795de7aee5SJames Chapman nla_put_u64(skb, L2TP_ATTR_RX_ERRORS, stats.rx_errors)) 68060aed2abSDavid S. Miller goto nla_put_failure; 681309795f4SJames Chapman nla_nest_end(skb, nest); 682309795f4SJames Chapman 683309795f4SJames Chapman return genlmsg_end(skb, hdr); 684309795f4SJames Chapman 685309795f4SJames Chapman nla_put_failure: 686309795f4SJames Chapman genlmsg_cancel(skb, hdr); 687309795f4SJames Chapman return -1; 688309795f4SJames Chapman } 689309795f4SJames Chapman 690309795f4SJames Chapman static int l2tp_nl_cmd_session_get(struct sk_buff *skb, struct genl_info *info) 691309795f4SJames Chapman { 692309795f4SJames Chapman struct l2tp_session *session; 693309795f4SJames Chapman struct sk_buff *msg; 694309795f4SJames Chapman int ret; 695309795f4SJames Chapman 696309795f4SJames Chapman session = l2tp_nl_session_find(info); 697309795f4SJames Chapman if (session == NULL) { 698309795f4SJames Chapman ret = -ENODEV; 699309795f4SJames Chapman goto out; 700309795f4SJames Chapman } 701309795f4SJames Chapman 702*58050fceSThomas Graf msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); 703309795f4SJames Chapman if (!msg) { 704309795f4SJames Chapman ret = -ENOMEM; 705309795f4SJames Chapman goto out; 706309795f4SJames Chapman } 707309795f4SJames Chapman 708309795f4SJames Chapman ret = l2tp_nl_session_send(msg, info->snd_pid, info->snd_seq, 709309795f4SJames Chapman 0, session); 710309795f4SJames Chapman if (ret < 0) 711309795f4SJames Chapman goto err_out; 712309795f4SJames Chapman 713309795f4SJames Chapman return genlmsg_unicast(genl_info_net(info), msg, info->snd_pid); 714309795f4SJames Chapman 715309795f4SJames Chapman err_out: 716309795f4SJames Chapman nlmsg_free(msg); 717309795f4SJames Chapman 718309795f4SJames Chapman out: 719309795f4SJames Chapman return ret; 720309795f4SJames Chapman } 721309795f4SJames Chapman 722309795f4SJames Chapman static int l2tp_nl_cmd_session_dump(struct sk_buff *skb, struct netlink_callback *cb) 723309795f4SJames Chapman { 724309795f4SJames Chapman struct net *net = sock_net(skb->sk); 725309795f4SJames Chapman struct l2tp_session *session; 726309795f4SJames Chapman struct l2tp_tunnel *tunnel = NULL; 727309795f4SJames Chapman int ti = cb->args[0]; 728309795f4SJames Chapman int si = cb->args[1]; 729309795f4SJames Chapman 730309795f4SJames Chapman for (;;) { 731309795f4SJames Chapman if (tunnel == NULL) { 732309795f4SJames Chapman tunnel = l2tp_tunnel_find_nth(net, ti); 733309795f4SJames Chapman if (tunnel == NULL) 734309795f4SJames Chapman goto out; 735309795f4SJames Chapman } 736309795f4SJames Chapman 737309795f4SJames Chapman session = l2tp_session_find_nth(tunnel, si); 738309795f4SJames Chapman if (session == NULL) { 739309795f4SJames Chapman ti++; 740309795f4SJames Chapman tunnel = NULL; 741309795f4SJames Chapman si = 0; 742309795f4SJames Chapman continue; 743309795f4SJames Chapman } 744309795f4SJames Chapman 745309795f4SJames Chapman if (l2tp_nl_session_send(skb, NETLINK_CB(cb->skb).pid, 746309795f4SJames Chapman cb->nlh->nlmsg_seq, NLM_F_MULTI, 747309795f4SJames Chapman session) <= 0) 748309795f4SJames Chapman break; 749309795f4SJames Chapman 750309795f4SJames Chapman si++; 751309795f4SJames Chapman } 752309795f4SJames Chapman 753309795f4SJames Chapman out: 754309795f4SJames Chapman cb->args[0] = ti; 755309795f4SJames Chapman cb->args[1] = si; 756309795f4SJames Chapman 757309795f4SJames Chapman return skb->len; 758309795f4SJames Chapman } 759309795f4SJames Chapman 760309795f4SJames Chapman static struct nla_policy l2tp_nl_policy[L2TP_ATTR_MAX + 1] = { 761309795f4SJames Chapman [L2TP_ATTR_NONE] = { .type = NLA_UNSPEC, }, 762309795f4SJames Chapman [L2TP_ATTR_PW_TYPE] = { .type = NLA_U16, }, 763309795f4SJames Chapman [L2TP_ATTR_ENCAP_TYPE] = { .type = NLA_U16, }, 764309795f4SJames Chapman [L2TP_ATTR_OFFSET] = { .type = NLA_U16, }, 765309795f4SJames Chapman [L2TP_ATTR_DATA_SEQ] = { .type = NLA_U8, }, 766309795f4SJames Chapman [L2TP_ATTR_L2SPEC_TYPE] = { .type = NLA_U8, }, 767309795f4SJames Chapman [L2TP_ATTR_L2SPEC_LEN] = { .type = NLA_U8, }, 768309795f4SJames Chapman [L2TP_ATTR_PROTO_VERSION] = { .type = NLA_U8, }, 769309795f4SJames Chapman [L2TP_ATTR_CONN_ID] = { .type = NLA_U32, }, 770309795f4SJames Chapman [L2TP_ATTR_PEER_CONN_ID] = { .type = NLA_U32, }, 771309795f4SJames Chapman [L2TP_ATTR_SESSION_ID] = { .type = NLA_U32, }, 772309795f4SJames Chapman [L2TP_ATTR_PEER_SESSION_ID] = { .type = NLA_U32, }, 773309795f4SJames Chapman [L2TP_ATTR_UDP_CSUM] = { .type = NLA_U8, }, 774309795f4SJames Chapman [L2TP_ATTR_VLAN_ID] = { .type = NLA_U16, }, 775309795f4SJames Chapman [L2TP_ATTR_DEBUG] = { .type = NLA_U32, }, 776309795f4SJames Chapman [L2TP_ATTR_RECV_SEQ] = { .type = NLA_U8, }, 777309795f4SJames Chapman [L2TP_ATTR_SEND_SEQ] = { .type = NLA_U8, }, 778309795f4SJames Chapman [L2TP_ATTR_LNS_MODE] = { .type = NLA_U8, }, 779309795f4SJames Chapman [L2TP_ATTR_USING_IPSEC] = { .type = NLA_U8, }, 780309795f4SJames Chapman [L2TP_ATTR_RECV_TIMEOUT] = { .type = NLA_MSECS, }, 781309795f4SJames Chapman [L2TP_ATTR_FD] = { .type = NLA_U32, }, 782309795f4SJames Chapman [L2TP_ATTR_IP_SADDR] = { .type = NLA_U32, }, 783309795f4SJames Chapman [L2TP_ATTR_IP_DADDR] = { .type = NLA_U32, }, 784309795f4SJames Chapman [L2TP_ATTR_UDP_SPORT] = { .type = NLA_U16, }, 785309795f4SJames Chapman [L2TP_ATTR_UDP_DPORT] = { .type = NLA_U16, }, 786309795f4SJames Chapman [L2TP_ATTR_MTU] = { .type = NLA_U16, }, 787309795f4SJames Chapman [L2TP_ATTR_MRU] = { .type = NLA_U16, }, 788309795f4SJames Chapman [L2TP_ATTR_STATS] = { .type = NLA_NESTED, }, 789f9bac8dfSChris Elston [L2TP_ATTR_IP6_SADDR] = { 790f9bac8dfSChris Elston .type = NLA_BINARY, 791f9bac8dfSChris Elston .len = sizeof(struct in6_addr), 792f9bac8dfSChris Elston }, 793f9bac8dfSChris Elston [L2TP_ATTR_IP6_DADDR] = { 794f9bac8dfSChris Elston .type = NLA_BINARY, 795f9bac8dfSChris Elston .len = sizeof(struct in6_addr), 796f9bac8dfSChris Elston }, 797309795f4SJames Chapman [L2TP_ATTR_IFNAME] = { 798309795f4SJames Chapman .type = NLA_NUL_STRING, 799309795f4SJames Chapman .len = IFNAMSIZ - 1, 800309795f4SJames Chapman }, 801309795f4SJames Chapman [L2TP_ATTR_COOKIE] = { 802309795f4SJames Chapman .type = NLA_BINARY, 803309795f4SJames Chapman .len = 8, 804309795f4SJames Chapman }, 805309795f4SJames Chapman [L2TP_ATTR_PEER_COOKIE] = { 806309795f4SJames Chapman .type = NLA_BINARY, 807309795f4SJames Chapman .len = 8, 808309795f4SJames Chapman }, 809309795f4SJames Chapman }; 810309795f4SJames Chapman 811309795f4SJames Chapman static struct genl_ops l2tp_nl_ops[] = { 812309795f4SJames Chapman { 813309795f4SJames Chapman .cmd = L2TP_CMD_NOOP, 814309795f4SJames Chapman .doit = l2tp_nl_cmd_noop, 815309795f4SJames Chapman .policy = l2tp_nl_policy, 816309795f4SJames Chapman /* can be retrieved by unprivileged users */ 817309795f4SJames Chapman }, 818309795f4SJames Chapman { 819309795f4SJames Chapman .cmd = L2TP_CMD_TUNNEL_CREATE, 820309795f4SJames Chapman .doit = l2tp_nl_cmd_tunnel_create, 821309795f4SJames Chapman .policy = l2tp_nl_policy, 822309795f4SJames Chapman .flags = GENL_ADMIN_PERM, 823309795f4SJames Chapman }, 824309795f4SJames Chapman { 825309795f4SJames Chapman .cmd = L2TP_CMD_TUNNEL_DELETE, 826309795f4SJames Chapman .doit = l2tp_nl_cmd_tunnel_delete, 827309795f4SJames Chapman .policy = l2tp_nl_policy, 828309795f4SJames Chapman .flags = GENL_ADMIN_PERM, 829309795f4SJames Chapman }, 830309795f4SJames Chapman { 831309795f4SJames Chapman .cmd = L2TP_CMD_TUNNEL_MODIFY, 832309795f4SJames Chapman .doit = l2tp_nl_cmd_tunnel_modify, 833309795f4SJames Chapman .policy = l2tp_nl_policy, 834309795f4SJames Chapman .flags = GENL_ADMIN_PERM, 835309795f4SJames Chapman }, 836309795f4SJames Chapman { 837309795f4SJames Chapman .cmd = L2TP_CMD_TUNNEL_GET, 838309795f4SJames Chapman .doit = l2tp_nl_cmd_tunnel_get, 839309795f4SJames Chapman .dumpit = l2tp_nl_cmd_tunnel_dump, 840309795f4SJames Chapman .policy = l2tp_nl_policy, 841309795f4SJames Chapman .flags = GENL_ADMIN_PERM, 842309795f4SJames Chapman }, 843309795f4SJames Chapman { 844309795f4SJames Chapman .cmd = L2TP_CMD_SESSION_CREATE, 845309795f4SJames Chapman .doit = l2tp_nl_cmd_session_create, 846309795f4SJames Chapman .policy = l2tp_nl_policy, 847309795f4SJames Chapman .flags = GENL_ADMIN_PERM, 848309795f4SJames Chapman }, 849309795f4SJames Chapman { 850309795f4SJames Chapman .cmd = L2TP_CMD_SESSION_DELETE, 851309795f4SJames Chapman .doit = l2tp_nl_cmd_session_delete, 852309795f4SJames Chapman .policy = l2tp_nl_policy, 853309795f4SJames Chapman .flags = GENL_ADMIN_PERM, 854309795f4SJames Chapman }, 855309795f4SJames Chapman { 856309795f4SJames Chapman .cmd = L2TP_CMD_SESSION_MODIFY, 857309795f4SJames Chapman .doit = l2tp_nl_cmd_session_modify, 858309795f4SJames Chapman .policy = l2tp_nl_policy, 859309795f4SJames Chapman .flags = GENL_ADMIN_PERM, 860309795f4SJames Chapman }, 861309795f4SJames Chapman { 862309795f4SJames Chapman .cmd = L2TP_CMD_SESSION_GET, 863309795f4SJames Chapman .doit = l2tp_nl_cmd_session_get, 864309795f4SJames Chapman .dumpit = l2tp_nl_cmd_session_dump, 865309795f4SJames Chapman .policy = l2tp_nl_policy, 866309795f4SJames Chapman .flags = GENL_ADMIN_PERM, 867309795f4SJames Chapman }, 868309795f4SJames Chapman }; 869309795f4SJames Chapman 870309795f4SJames Chapman int l2tp_nl_register_ops(enum l2tp_pwtype pw_type, const struct l2tp_nl_cmd_ops *ops) 871309795f4SJames Chapman { 872309795f4SJames Chapman int ret; 873309795f4SJames Chapman 874309795f4SJames Chapman ret = -EINVAL; 875309795f4SJames Chapman if (pw_type >= __L2TP_PWTYPE_MAX) 876309795f4SJames Chapman goto err; 877309795f4SJames Chapman 878309795f4SJames Chapman genl_lock(); 879309795f4SJames Chapman ret = -EBUSY; 880309795f4SJames Chapman if (l2tp_nl_cmd_ops[pw_type]) 881309795f4SJames Chapman goto out; 882309795f4SJames Chapman 883309795f4SJames Chapman l2tp_nl_cmd_ops[pw_type] = ops; 8848cb49014SDavid S. Miller ret = 0; 885309795f4SJames Chapman 886309795f4SJames Chapman out: 887309795f4SJames Chapman genl_unlock(); 888309795f4SJames Chapman err: 8898cb49014SDavid S. Miller return ret; 890309795f4SJames Chapman } 891309795f4SJames Chapman EXPORT_SYMBOL_GPL(l2tp_nl_register_ops); 892309795f4SJames Chapman 893309795f4SJames Chapman void l2tp_nl_unregister_ops(enum l2tp_pwtype pw_type) 894309795f4SJames Chapman { 895309795f4SJames Chapman if (pw_type < __L2TP_PWTYPE_MAX) { 896309795f4SJames Chapman genl_lock(); 897309795f4SJames Chapman l2tp_nl_cmd_ops[pw_type] = NULL; 898309795f4SJames Chapman genl_unlock(); 899309795f4SJames Chapman } 900309795f4SJames Chapman } 901309795f4SJames Chapman EXPORT_SYMBOL_GPL(l2tp_nl_unregister_ops); 902309795f4SJames Chapman 903309795f4SJames Chapman static int l2tp_nl_init(void) 904309795f4SJames Chapman { 905309795f4SJames Chapman int err; 906309795f4SJames Chapman 907a4ca44faSJoe Perches pr_info("L2TP netlink interface\n"); 908309795f4SJames Chapman err = genl_register_family_with_ops(&l2tp_nl_family, l2tp_nl_ops, 909309795f4SJames Chapman ARRAY_SIZE(l2tp_nl_ops)); 910309795f4SJames Chapman 911309795f4SJames Chapman return err; 912309795f4SJames Chapman } 913309795f4SJames Chapman 914309795f4SJames Chapman static void l2tp_nl_cleanup(void) 915309795f4SJames Chapman { 916309795f4SJames Chapman genl_unregister_family(&l2tp_nl_family); 917309795f4SJames Chapman } 918309795f4SJames Chapman 919309795f4SJames Chapman module_init(l2tp_nl_init); 920309795f4SJames Chapman module_exit(l2tp_nl_cleanup); 921309795f4SJames Chapman 922309795f4SJames Chapman MODULE_AUTHOR("James Chapman <jchapman@katalix.com>"); 923309795f4SJames Chapman MODULE_DESCRIPTION("L2TP netlink"); 924309795f4SJames Chapman MODULE_LICENSE("GPL"); 925309795f4SJames Chapman MODULE_VERSION("1.0"); 926e9412c37SNeil Horman MODULE_ALIAS_GENL_FAMILY("l2tp"); 927