// SPDX-License-Identifier: GPL-2.0-only
/* L2TP netlink layer, for management
*
* Copyright (c) 2008,2009,2010 Katalix Systems Ltd
*
* Partly based on the IrDA nelink implementation
* (see net/irda/irnetlink.c) which is:
* Copyright (c) 2007 Samuel Ortiz <samuel@sortiz.org>
* which is in turn partly based on the wireless netlink code:
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <net/sock.h>
#include <net/genetlink.h>
#include <net/udp.h>
#include <linux/in.h>
#include <linux/udp.h>
#include <linux/socket.h>
#include <linux/module.h>
#include <linux/list.h>
#include <net/net_namespace.h>
#include <linux/l2tp.h>
#include "l2tp_core.h"
static struct genl_family l2tp_nl_family;
static const struct genl_multicast_group l2tp_multicast_group[] = {
{
.name = L2TP_GENL_MCGROUP,
},
};
static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 portid, u32 seq,
int flags, struct l2tp_tunnel *tunnel, u8 cmd);
static int l2tp_nl_session_send(struct sk_buff *skb, u32 portid, u32 seq,
int flags, struct l2tp_session *session,
u8 cmd);
/* Accessed under genl lock */
static const struct l2tp_nl_cmd_ops *l2tp_nl_cmd_ops[__L2TP_PWTYPE_MAX];
static struct l2tp_session *l2tp_nl_session_get(struct genl_info *info)
{
u32 tunnel_id;
u32 session_id;
char *ifname;
struct l2tp_tunnel *tunnel;
struct l2tp_session *session = NULL;
struct net *net = genl_info_net(info);
if (info->attrs[L2TP_ATTR_IFNAME]) {
ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]);
session = l2tp_session_get_by_ifname(net, ifname);
} else if ((info->attrs[L2TP_ATTR_SESSION_ID]) &&
(info->attrs[L2TP_ATTR_CONN_ID])) {
tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]);
tunnel = l2tp_tunnel_get(net, tunnel_id);
if (tunnel) {
session = l2tp_tunnel_get_session(tunnel, session_id);
l2tp_tunnel_dec_refcount(tunnel);
}
}
return session;
}
static int l2tp_nl_cmd_noop(struct sk_buff *skb, struct genl_info *info)
{
struct sk_buff *msg;
void *hdr;
int ret = -ENOBUFS;
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg) {
ret = -ENOMEM;
goto out;
}
hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
&l2tp_nl_family, 0, L2TP_CMD_NOOP);
if (!hdr) {
ret = -EMSGSIZE;
goto err_out;
}
genlmsg_end(msg, hdr);
return genlmsg_unicast(genl_info_net(info), msg, info->snd_portid);
err_out:
nlmsg_free(msg);
out:
return ret;
}
static int l2tp_tunnel_notify(struct genl_family *family,
struct genl_info *info,
struct l2tp_tunnel *tunnel,
u8 cmd)
{
struct sk_buff *msg;
int ret;
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg)
return -ENOMEM;
ret = l2tp_nl_tunnel_send(msg, info->snd_portid, info->snd_seq,
NLM_F_ACK, tunnel, cmd);
if (ret >= 0) {
ret = genlmsg_multicast_allns(family, msg, 0, 0, GFP_ATOMIC);
/* We don't care if no one is listening */
if (ret == -ESRCH)
ret = 0;
return ret;
}
nlmsg_free(msg);
return ret;
}
static int l2tp_session_notify(struct genl_family *family,
struct genl_info *info,
struct l2tp_session *session,
u8 cmd)
{
struct sk_buff *msg;
int ret;
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg)
return -ENOMEM;
ret = l2tp_nl_session_send(msg, info->snd_portid, info->snd_seq,
NLM_F_ACK, session, cmd);
if (ret >= 0) {
ret = genlmsg_multicast_allns(family, msg, 0, 0, GFP_ATOMIC);
/* We don't care if no one is listening */
if (ret == -ESRCH)
ret = 0;
return ret;
}
nlmsg_free(msg);
return ret;
}
static int l2tp_nl_cmd_tunnel_create_get_addr(struct nlattr **attrs, struct l2tp_tunnel_cfg *cfg)
{
if (attrs[L2TP_ATTR_UDP_SPORT])
cfg->local_udp_port = nla_get_u16(attrs[L2TP_ATTR_UDP_SPORT]);
if (attrs[L2TP_ATTR_UDP_DPORT])
cfg->peer_udp_port = nla_get_u16(attrs[L2TP_ATTR_UDP_DPORT]);
cfg->use_udp_checksums = nla_get_flag(attrs[L2TP_ATTR_UDP_CSUM]);
/* Must have either AF_INET or AF_INET6 address for source and destination */
#if IS_ENABLED(CONFIG_IPV6)
if (attrs[L2TP_ATTR_IP6_SADDR] && attrs[L2TP_ATTR_IP6_DADDR]) {
cfg->local_ip6 = nla_data(attrs[L2TP_ATTR_IP6_SADDR]);
cfg->peer_ip6 = nla_data(attrs[L2TP_ATTR_IP6_DADDR]);
cfg->udp6_zero_tx_checksums = nla_get_flag(attrs[L2TP_ATTR_UDP_ZERO_CSUM6_TX]);
cfg->udp6_zero_rx_checksums = nla_get_flag(attrs[L2TP_ATTR_UDP_ZERO_CSUM6_RX]);
return 0