// SPDX-License-Identifier: GPL-2.0
/*
* Management Component Transport Protocol (MCTP)
*
* Copyright (c) 2021 Code Construct
* Copyright (c) 2021 Google
*/
#include <linux/compat.h>
#include <linux/if_arp.h>
#include <linux/net.h>
#include <linux/mctp.h>
#include <linux/module.h>
#include <linux/socket.h>
#include <net/mctp.h>
#include <net/mctpdevice.h>
#include <net/sock.h>
#define CREATE_TRACE_POINTS
#include <trace/events/mctp.h>
/* socket implementation */
static void mctp_sk_expire_keys(struct timer_list *timer);
static int mctp_release(struct socket *sock)
{
struct sock *sk = sock->sk;
if (sk) {
sock->sk = NULL;
sk->sk_prot->close(sk, 0);
}
return 0;
}
/* Generic sockaddr checks, padding checks only so far */
static bool mctp_sockaddr_is_ok(const struct sockaddr_mctp *addr)
{
return !addr->__smctp_pad0 && !addr->__smctp_pad1;
}
static bool mctp_sockaddr_ext_is_ok(const struct sockaddr_mctp_ext *addr)
{
return !addr->__smctp_pad0[0] &&
!addr->__smctp_pad0[1] &&
!addr->__smctp_pad0[2];
}
static int mctp_bind(struct socket *sock, struct sockaddr_unsized *addr, int addrlen)
{
struct sock *sk = sock->sk;
struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk);
struct net *net = sock_net(&msk->sk);
struct sockaddr_mctp *smctp;
int rc;
if (addrlen < sizeof(*smctp))
return -EINVAL;
if (addr->sa_family != AF_MCTP)
return -EAFNOSUPPORT;
if (!capable(CAP_NET_BIND_SERVICE))
return -EACCES;
/* it's a valid sockaddr for MCTP, cast and do protocol checks */
smctp = (struct sockaddr_mctp *)addr;
if (!mctp_sockaddr_is_ok(smctp))
return -EINVAL;
lock_sock(sk);
if (sk_hashed(sk)) {
rc = -EADDRINUSE;
goto out_release;
}
msk->bind_local_addr = smctp->smctp_addr.s_addr;
/* MCTP_NET_ANY with a specific EID is resolved to the default net
* at bind() time.
* For bind_addr=MCTP_ADDR_ANY it is handled specially at route
* lookup time.
*/
if (smctp->smctp_network == MCTP_NET_ANY &&
msk->bind_local_addr != MCTP_ADDR_ANY) {
msk->bind_net = mctp_default_net(net);
} else {
msk->bind_net = smctp->smctp_network;
}
/* ignore the IC bit */
smctp->smctp_type &= 0x7f;
if (msk->bind_peer_set) {
if (msk->bind_type != smctp->smctp_type) {
/* Prior connect() had a different type */
rc = -EINVAL;
goto out_release;
}
if (msk->bind_net == MCTP_NET_ANY) {
/* Restrict to the network passed to connect() */
msk->bind_net = msk->bind_peer_net;
}
if (msk->bind_net != msk->bind_peer_net) {
/* connect() had a different net to bind() */
rc = -EINVAL;
goto out_release;
}
} else {
msk->bind_type = smctp->smctp_type;
}
rc = sk->sk_prot->hash(sk);
out_release:
release_sock(sk);
return rc;
}
/* Used to set a specific peer prior to bind. Not used for outbound
* connections (Tag Owner set) since MCTP is a datagram protocol.
*/
static int mctp_connect(struct socket *sock, struct sockaddr_unsized *addr,
int addrlen, int flags)
{
struct sock *sk = sock->sk;
struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk);
struct net *net = sock_net(&msk->sk);
struct sockaddr_mctp *smctp;
int rc;
if (addrlen != sizeof(*smctp))
return -EINVAL;
if (addr->sa_family != AF_MCTP)
return -EAFNOSUPPORT;
/* It's a valid sockaddr for MCTP, cast and do protocol checks */
smctp = (struct sockaddr_mctp *)addr;
if (!mctp_sockaddr_is_ok(smctp))
return -EINVAL;
/* Can't bind by tag */
if (smctp->smctp_tag)
return -EINVAL;
/* IC bit must be unset */
if (smctp->smctp_type & 0x80)
return -EINVAL;
lock_sock(sk);
if (sk_hashed(sk)) {
/* bind() already */
rc = -EADDRINUSE;
goto out_release;
}
if (msk->bind_peer_set) {
/* connect() already */
rc = -EADDRINUSE;
goto out_release;
}
msk->bind_peer_set = true;
msk->bind_peer_addr = smctp->smctp_addr.s_addr;
msk